papierkram_api_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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