papierkram_api_client 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.
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Banking
6
+ # This class is responsible for all the API calls related to banking bank connections.
7
+ class BankConnections < Api::V1::Base
8
+ def by(id:)
9
+ get("#{@url_api_path}/banking/bank_connections/#{id}")
10
+ end
11
+
12
+ def all(page: 1,
13
+ page_size: 100,
14
+ order_by: nil,
15
+ order_direction: nil)
16
+ query = {
17
+ page: page,
18
+ page_size: page_size
19
+ }
20
+ query[:order_by] = order_by if order_by
21
+ query[:order_direction] = order_direction if order_direction
22
+
23
+ get("#{@url_api_path}/banking/bank_connections", query)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Banking
6
+ # This class is responsible for all the API calls related to banking transactions.
7
+ class Transactions
8
+ def by(id:)
9
+ # wip
10
+ end
11
+
12
+ def all
13
+ # wip
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ # This class is the base class for all the API calls.
6
+ class Base
7
+ attr_reader :client, :url_api_path
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ @url_api_path = '/api/v1'
12
+ end
13
+
14
+ def remaining_quota(response)
15
+ return response.headers['x-remaining-quota'].to_i if response.is_a?(Faraday::Response)
16
+ raise ArgumentError, 'Invalid response object' unless response.is_a?(Hash)
17
+
18
+ quota = response.dig('headers', 'x-remaining-quota') || response['x-remaining-quota']
19
+ return Integer(quota) if quota
20
+
21
+ raise ArgumentError, "No remaining quota found in response: #{response}"
22
+ end
23
+
24
+ def get(url, params = {}, headers = {})
25
+ validate_get!(params)
26
+ call_wrapper!(:get, url, params, headers)
27
+ end
28
+
29
+ def post(url, params = {}, headers = {})
30
+ call_wrapper!(:post, url, params, headers)
31
+ end
32
+
33
+ def put(url, params = {}, headers = {})
34
+ call_wrapper!(:put, url, params, headers)
35
+ end
36
+
37
+ def patch(url, params = {}, headers = {})
38
+ call_wrapper!(:patch, url, params, headers)
39
+ end
40
+
41
+ def delete(url, params = {}, headers = {})
42
+ call_wrapper!(:delete, url, params, headers)
43
+ end
44
+
45
+ private
46
+
47
+ def call_wrapper!(method, url, params = {}, headers = {})
48
+ @client.send(method, url, params, headers)
49
+ end
50
+
51
+ def validate_order_direction!(order_direction)
52
+ return if order_direction.nil?
53
+
54
+ raise ArgumentError, 'Invalid order direction' unless %w[asc desc].include?(order_direction)
55
+ end
56
+
57
+ def validate_billing_state!(billing_state)
58
+ return if billing_state.nil?
59
+
60
+ raise ArgumentError, 'Invalid billing state' unless %w[billed
61
+ unbilled
62
+ billable
63
+ unbillable
64
+ archived].include?(billing_state)
65
+ end
66
+
67
+ def validate_get!(params)
68
+ return unless params
69
+
70
+ validate_order_direction!(params[:order_direction])
71
+ validate_billing_state!(params[:billing_state])
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Contact
6
+ # This class is responsible for all the API calls related to banking bank connections.
7
+ class Companies < Api::V1::Base
8
+ def by(id:)
9
+ get("#{@url_api_path}/contact/companies/#{id}")
10
+ end
11
+
12
+ def all(page: 1,
13
+ page_size: 100,
14
+ order_by: nil,
15
+ order_direction: nil)
16
+ query = {
17
+ page: page,
18
+ page_size: page_size
19
+ }
20
+ query[:order_by] = order_by if order_by
21
+ query[:order_direction] = order_direction if order_direction
22
+
23
+ get("#{@url_api_path}/contact/companies", query)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Contact
6
+ # This class is responsible for all the API calls related to companies' persons connections.
7
+ class CompaniesPersons < Api::V1::Base
8
+ def by(company_id:, id:)
9
+ get("#{@url_api_path}/contact/companies/#{company_id}/persons/#{id}")
10
+ end
11
+
12
+ def all(company_id:, page: 1, page_size: 100, order_by: nil, order_direction: nil)
13
+ query = {
14
+ company_id: company_id,
15
+ page: page,
16
+ page_size: page_size
17
+ }
18
+ query[:order_by] = order_by if order_by
19
+ query[:order_direction] = order_direction if order_direction
20
+
21
+ get("#{@url_api_path}/contact/companies/#{company_id}/persons", query)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Expense
6
+ # This class is responsible for all the API calls related to expense vouchers.
7
+ class Vouchers < Api::V1::Base
8
+ def by(id:, pdf: false)
9
+ if pdf == true
10
+ return get("#{@url_api_path}/expense/vouchers/#{id}/pdf", nil,
11
+ { headers: { 'Content-Type' => 'application/pdf' } })
12
+ end
13
+
14
+ get("#{@url_api_path}/expense/vouchers/#{id}")
15
+ end
16
+
17
+ def all(page: 1, # rubocop:disable Metrics/CyclomaticComplexity
18
+ page_size: 100,
19
+ order_by: nil,
20
+ order_direction: nil,
21
+ creditor_id: nil,
22
+ project_id: nil,
23
+ document_date_range_start: nil,
24
+ document_date_range_end: nil)
25
+ query = {
26
+ page: page,
27
+ page_size: page_size
28
+ }
29
+ query[:order_by] = order_by if order_by
30
+ query[:order_direction] = order_direction if order_direction
31
+ query[:creditor_id] = creditor_id if creditor_id
32
+ query[:project_id] = project_id if project_id
33
+ query[:document_date_range_start] = document_date_range_start if document_date_range_start
34
+ if document_date_range_end && document_date_range_start
35
+ query[:document_date_range_end] =
36
+ document_date_range_end
37
+ end
38
+
39
+ get("#{@url_api_path}/expense/vouchers", query)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Helper
6
+ # This class is responsible to convert a response to a pdf file.
7
+ class PdfFromResponse
8
+ def initialize(response)
9
+ @response = response
10
+ end
11
+
12
+ def to_pdf(filename = Time.now.to_s)
13
+ raise ArgumentError, 'Response is not a Faraday::Response' unless @response.is_a?(Faraday::Response)
14
+ raise ArgumentError, 'Response is not a PDF file' unless response.body.start_with?('%PDF-1.4')
15
+
16
+ file = Tempfile.new([filename.to_s, '.pdf'], binmode: true)
17
+ file.write(response.body)
18
+ file.close
19
+
20
+ { response: @response, path_to_pdf_file: file.path }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Income
6
+ # This class is responsible for all the API calls related to income estimates.
7
+ class Estimates < Api::V1::Base
8
+ def by(id:, pdf: false)
9
+ if pdf == true
10
+ return get("#{@url_api_path}/income/estimates/#{id}/pdf", nil,
11
+ { headers: { 'Content-Type' => 'application/pdf' } })
12
+ end
13
+
14
+ get("#{@url_api_path}/income/estimates/#{id}")
15
+ end
16
+
17
+ def all(page: 1, # rubocop:disable Metrics/CyclomaticComplexity
18
+ page_size: 100,
19
+ order_by: nil,
20
+ order_direction: nil,
21
+ creditor_id: nil,
22
+ project_id: nil,
23
+ document_date_range_start: nil,
24
+ document_date_range_end: nil)
25
+ query = {
26
+ page: page,
27
+ page_size: page_size
28
+ }
29
+ query[:order_by] = order_by if order_by
30
+ query[:order_direction] = order_direction if order_direction
31
+ query[:creditor_id] = creditor_id if creditor_id
32
+ query[:project_id] = project_id if project_id
33
+ query[:document_date_range_start] = document_date_range_start if document_date_range_start
34
+ if document_date_range_end && document_date_range_start
35
+ query[:document_date_range_end] =
36
+ document_date_range_end
37
+ end
38
+
39
+ get("#{@url_api_path}/income/estimates", query)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Income
6
+ # This class is responsible for all the API calls related to income invoices.
7
+ class Invoices < Api::V1::Base
8
+ def by(id:, pdf: false)
9
+ if pdf == true
10
+ return get("#{@url_api_path}/income/invoices/#{id}/pdf", nil,
11
+ { headers: { 'Content-Type' => 'application/pdf' } })
12
+ end
13
+
14
+ get("#{@url_api_path}/income/invoices/#{id}")
15
+ end
16
+
17
+ def all(page: 1, # rubocop:disable Metrics/CyclomaticComplexity
18
+ page_size: 100,
19
+ order_by: nil,
20
+ order_direction: nil,
21
+ creditor_id: nil,
22
+ project_id: nil,
23
+ document_date_range_start: nil,
24
+ document_date_range_end: nil)
25
+ query = {
26
+ page: page,
27
+ page_size: page_size
28
+ }
29
+ query[:order_by] = order_by if order_by
30
+ query[:order_direction] = order_direction if order_direction
31
+ query[:creditor_id] = creditor_id if creditor_id
32
+ query[:project_id] = project_id if project_id
33
+ query[:document_date_range_start] = document_date_range_start if document_date_range_start
34
+ if document_date_range_end && document_date_range_start
35
+ query[:document_date_range_end] =
36
+ document_date_range_end
37
+ end
38
+
39
+ get("#{@url_api_path}/income/invoices", query)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Income
6
+ # This class is responsible for all the API calls related to income propositions.
7
+ class Propositions < Api::V1::Base
8
+ def by(id:)
9
+ get("#{@url_api_path}/income/propositions/#{id}")
10
+ end
11
+
12
+ def all
13
+ get("#{@url_api_path}/income/propositions")
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ # This class is responsible for all the API calls related to info connections.
6
+ class Info < Api::V1::Base
7
+ def details
8
+ get("#{@url_api_path}/info")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ # This class is responsible for all the API calls related to projects connections.
6
+ class Projects < Api::V1::Base
7
+ def by(id:)
8
+ get("#{@url_api_path}/projects/#{id}")
9
+ end
10
+
11
+ def all(page: 1, per_page: 100, order_by: nil, order_direction: nil, company_id: nil)
12
+ query = {
13
+ page: page,
14
+ per_page: per_page
15
+ }
16
+ query[:order_by] = order_by if order_by
17
+ query[:order_direction] = order_direction if order_direction
18
+ query[:company_id] = company_id if company_id
19
+
20
+ get("#{@url_api_path}/projects", query)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Tracker
6
+ # This class is responsible for all the API calls related to tracker tasks connections.
7
+ class Tasks < Api::V1::Base
8
+ def by(id:)
9
+ get("#{@url_api_path}/tracker/tasks/#{id}")
10
+ end
11
+
12
+ def all(page: 1,
13
+ page_size: 100,
14
+ order_by: nil,
15
+ order_direction: nil,
16
+ project_id: nil,
17
+ proposition_id: nil)
18
+ query = {
19
+ page: page,
20
+ page_size: page_size
21
+ }
22
+ query[:order_by] = order_by if order_by
23
+ query[:order_direction] = order_direction if order_direction
24
+ query[:project_id] = project_id if project_id
25
+ query[:proposition_id] = proposition_id if proposition_id
26
+ get("#{@url_api_path}/tracker/tasks", query)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module V1
5
+ module Tracker
6
+ # This class is responsible for all the API calls related to tracker time entries connections.
7
+ class TimeEntries < Api::V1::Base
8
+ def by(id:)
9
+ get("#{@url_api_path}/tracker/time_entries/#{id}")
10
+ end
11
+
12
+ def all(page: 1, # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
13
+ page_size: 100,
14
+ order_by: nil,
15
+ order_direction: nil,
16
+ project_id: nil,
17
+ task_id: nil,
18
+ invoice_id: nil,
19
+ user_id: nil,
20
+ billing_state: nil,
21
+ start_time_range_start: nil,
22
+ start_time_range_end: nil)
23
+ validate!(billing_state: billing_state,
24
+ start_time_range_start: start_time_range_start,
25
+ start_time_range_end: start_time_range_end)
26
+
27
+ query = {
28
+ page: page,
29
+ page_size: page_size
30
+ }
31
+ query[:order_by] = order_by if order_by
32
+ query[:order_direction] = order_direction if order_direction
33
+ query[:project_id] = project_id if project_id
34
+ query[:task_id] = task_id if task_id
35
+ query[:invoice_id] = invoice_id if invoice_id
36
+ query[:user_id] = user_id if user_id
37
+ query[:billing_state] = billing_state if billing_state
38
+ query[:start_time_range_start] = start_time_range_start if start_time_range_start
39
+ query[:start_time_range_end] = start_time_range_end if start_time_range_end
40
+ get("#{@url_api_path}/tracker/time_entries", query)
41
+ end
42
+
43
+ private
44
+
45
+ def validate!(billing_state:, start_time_range_start:, start_time_range_end:)
46
+ if billing_state && !%w[billed unbilled billable unbillable archived].include?(billing_state)
47
+ raise ArgumentError, 'billing_state must be one of: "billed" "unbilled"" billable" "unbillable" "archived"'
48
+ end
49
+ if start_time_range_start && !start_time_range_start.is_a?(Time)
50
+ raise ArgumentError, 'start_time_range_start must be a Time object'
51
+ end
52
+ return unless start_time_range_end && !start_time_range_end.is_a?(Time)
53
+
54
+ raise ArgumentError, 'start_time_range_end must be a Time object'
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PapierkramApiClient
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'httpx/adapters/faraday'
5
+ require 'forwardable'
6
+ require 'tempfile'
7
+
8
+ require_relative 'papierkram_api_client/version'
9
+ require_relative 'api/v1/base'
10
+ require_relative 'api/v1/banking/bank_connections'
11
+ require_relative 'api/v1/banking/transactions'
12
+ require_relative 'api/v1/contact/companies'
13
+ require_relative 'api/v1/contact/companies_persons'
14
+ require_relative 'api/v1/expense/vouchers'
15
+ require_relative 'api/v1/income/estimates'
16
+ require_relative 'api/v1/income/invoices'
17
+ require_relative 'api/v1/income/propositions'
18
+ require_relative 'api/v1/info'
19
+ require_relative 'api/v1/projects'
20
+ require_relative 'api/v1/tracker/tasks'
21
+ require_relative 'api/v1/tracker/time_entries'
22
+
23
+ module PapierkramApiClient
24
+ class Error < StandardError; end
25
+
26
+ # The Client class is the main entry point for the Papierkram API Client.
27
+ class Client
28
+ attr_accessor :client,
29
+ :subdomain,
30
+ :api_key,
31
+ :base_url,
32
+ :remaining_quota
33
+
34
+ extend Forwardable
35
+ def_delegators :@client, :get, :post, :put, :patch, :delete
36
+
37
+ def initialize(subdomain = nil, api_key = nil)
38
+ @subdomain = subdomain || ENV.fetch('PAPIERKRAM_SUBDOMAIN', nil)
39
+ @api_key = api_key || ENV.fetch('PAPIERKRAM_API_KEY', nil)
40
+ @base_url = base_url_env
41
+ @remaining_quota = nil
42
+ build_client!
43
+ end
44
+
45
+ def build_client!
46
+ @client = Faraday.new(url: @base_url) do |config|
47
+ config.request :authorization, 'Bearer', @api_key
48
+ config.request :json
49
+ config.response :json
50
+ config.adapter :httpx
51
+ yield(config) if block_given?
52
+ end
53
+ end
54
+
55
+ def banking_bank_connections
56
+ @banking_bank_connections ||= Api::V1::Banking::BankConnections.new(@client)
57
+ end
58
+
59
+ def banking_transactions
60
+ raise ArgumentError, 'not implemented'
61
+ @banking_transactions ||= Api::V1::Banking::Transactions.new(@client)
62
+ end
63
+
64
+ def contact_companies
65
+ @contact_companies ||= Api::V1::Contact::Companies.new(@client)
66
+ end
67
+
68
+ def contact_companies_persons
69
+ @contact_companies_persons ||= Api::V1::Contact::CompaniesPersons.new(@client)
70
+ end
71
+
72
+ def expense_vouchers
73
+ @expense_vouchers ||= Api::V1::Expense::Vouchers.new(@client)
74
+ end
75
+
76
+ def income_estimates
77
+ @income_estimates ||= Api::V1::Income::Estimates.new(@client)
78
+ end
79
+
80
+ def income_invoices
81
+ @income_invoices ||= Api::V1::Income::Invoices.new(@client)
82
+ end
83
+
84
+ def income_propositions
85
+ @income_propositions ||= Api::V1::Income::Propositions.new(@client)
86
+ end
87
+
88
+ def info
89
+ @info ||= Api::V1::Info.new(@client)
90
+ end
91
+
92
+ def projects
93
+ @projects ||= Api::V1::Projects.new(@client)
94
+ end
95
+
96
+ def tracker_tasks
97
+ @tracker_tasks ||= Api::V1::Tracker::Tasks.new(@client)
98
+ end
99
+
100
+ def tracker_time_entries
101
+ @tracker_time_entries ||= Api::V1::Tracker::TimeEntries.new(@client)
102
+ end
103
+
104
+ private
105
+
106
+ def base_url_env
107
+ "https://#{@subdomain}.papierkram.de"
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,4 @@
1
+ module PapierkramApiClient
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end