m1_api 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 880305cc13e0cde33cdabd9c30e1d4b8dff25a337cb6363bbb574deff09fd656
4
- data.tar.gz: 03c84cca83c77623a41fe1715bcad2b5396fc440a3e0fccb0d2494017e81fa7f
3
+ metadata.gz: 475e1c6fb04452314313521cd450258bdbe267261bf832473296682027a35c9b
4
+ data.tar.gz: db5ebe7aaf45bed0c93ebc132d19d47017d5faba256ff489b6145c085d019c6d
5
5
  SHA512:
6
- metadata.gz: 518a8c44b1dedba1c6790d4383bf22102588c1c45208c733c38c5520cb5da97b7e89a715df7346ee1c1a5b115da48a5f2ac241b22c7e73936de703479eaad47e
7
- data.tar.gz: 61ad53f7ddac12bf53d60fe9eb4c6f78158f366ea45fd9ba8525746106bdb7f1f1e45ac81d32b00b27bf438f80329065818f5bcede0608cdf22a9507cb0b9fb7
6
+ metadata.gz: 25cf2ab1e505f41201feff61a3a02349531c8fd1bbc1a36259d80c4d1d9a4384e3759cf92832eaeb58bc7f6df306cdb2477f91cf895c0d801bf143265be4f885
7
+ data.tar.gz: 960d1e1f5650eead60a3a23396a37d8fd7e2fcaf7cf9324e38eef2b90ea24b8eb7c7addb533ee7e331a0f3ee4c3b6fb46d648ca01bf6c9899a97d78f238ec091
@@ -1,21 +1,38 @@
1
1
  require_relative './m1_api/yaml_helpers.rb'
2
2
 
3
+ # open browser
4
+ # open network logs
5
+ # save entire thing as har
6
+ # parse
7
+ # should match the logs against when the actions were performed
8
+
3
9
  class M1API
4
10
  # autoload :CLI, 'm1_api/cli'
5
11
  autoload :VERSION, 'm1_api/version'
6
12
 
7
- attr_accessor :api_config_file, :token
13
+ attr_accessor :api_config_file, :api_configs, :token, :accounts, :accounts_detail
14
+
8
15
 
9
- api_config_file = "#{__dir__}/m1_api/api_configs.yml"
16
+ def output(string)
17
+ puts string
18
+ end
10
19
 
11
- def initialize(username, password, api_config_file = nil)
12
- @api_config_file = api_config_file || "#{__dir__}/m1_api/api_configs.yml"
20
+ def initialize(username, password, api_config_file = "#{__dir__}/m1_api/api_configs.yml")
21
+ @accounts = {}
22
+ @accounts_detail = {}
23
+ load_config_file(api_config_file)
13
24
  credentials = { username: username, password: password }
14
- res = YamlHelpers.call_api_from_yml(@api_config_file, 'authenticate', credentials)
25
+ res = YamlHelpers.call_api_from_config(@api_configs, :authenticate, credentials)
15
26
  raise "failed to authenticate:\n\t#{res}" unless res[:code] == 200 && res[:body]['data']['authenticate']['result']['didSucceed']
16
27
  @token = res[:body]['data']['authenticate']['accessToken']
17
28
  end
18
29
 
30
+ def load_config_file(api_config_file)
31
+ # change it to reject both if either fails
32
+ @api_config_file = api_config_file
33
+ @api_configs = YamlHelpers.load_yaml(@api_config_file)
34
+ end
35
+
19
36
  def self.read_credentials(credentials_file=nil)
20
37
  if credentials_file
21
38
  credentials = YamlHelpers.load_yaml(credentials_file)
@@ -25,21 +42,50 @@ class M1API
25
42
  end
26
43
  end
27
44
 
28
- def check_status
29
- res = call_api_from_yml(@@api_config_file, 'check_status', token)
30
- puts res.inspect
31
- end
32
-
33
45
  def query_accounts
34
46
  accounts = {}
35
47
  data = { token: @token }
36
- id_res = YamlHelpers.call_api_from_yml(@api_config_file, 'list_account_ids', data)
37
- ids = id_res[:body]['data']['viewer']['_accounts1NFCow']['edges'].map { |account| account['node']['id'] }
38
- ids.each do |id|
39
- data[:account_id] = id
40
- account_res = YamlHelpers.call_api_from_yml(@api_config_file, 'query_account', data)
41
- accounts[id] = account_res[:body]['data']['node']
48
+ id_res = YamlHelpers.call_api_from_config(@api_configs, :list_account_ids, data)
49
+ ids = id_res[:body]['data']['viewer']['accounts']['edges'].each do |account|
50
+ accounts[account['node']['nickname']] = account['node']['id']
51
+ end
52
+ @accounts = accounts
53
+ end
54
+
55
+ def query_account_detail(account_id)
56
+ data = { token: @token, account_id: account_id }
57
+ detail = YamlHelpers.call_api_from_config(@api_configs, :query_account_detail, data)[:body]['data']['node']
58
+ @accounts_detail[account_id] = {
59
+ status: detail['status'],
60
+ bank: detail['lastAchRelationship'],
61
+ transfers: detail['_achTransfers']['edges']
62
+ }
63
+ @accounts_detail
64
+ end
65
+
66
+ def deposit(action, account_id, bank_id, transaction)
67
+ case action
68
+ when 'deposit'
69
+ data = { token: @token, account_id: account_id, bank_id: bank_id, amount: transaction }
70
+ YamlHelpers.call_api_from_config(@api_configs, :deposit, data)
71
+ when 'cancel'
72
+ data = { token: @token, account_id: account_id, bank_id: bank_id, transfer_id: transaction }
73
+ YamlHelpers.call_api_from_config(@api_configs, :cancel_deposit, data)
74
+ else
75
+ output "Invalid deposit action: '#{action}'"
76
+ end
77
+ end
78
+
79
+ def withdraw(action, account_id, bank_id, transaction)
80
+ case action
81
+ when 'withdraw'
82
+ data = { token: @token, account_id: account_id, bank_id: bank_id, amount: transaction }
83
+ YamlHelpers.call_api_from_config(@api_configs, :withdraw, data)
84
+ when 'cancel'
85
+ data = { token: @token, account_id: account_id, bank_id: bank_id, transfer_id: transaction }
86
+ YamlHelpers.call_api_from_config(@api_configs, :cancel_withdraw, data)
87
+ else
88
+ output "Invalid withdraw action: '#{action}'"
42
89
  end
43
- accounts
44
90
  end
45
91
  end
@@ -7,7 +7,7 @@
7
7
  :Content-Type: application/json
8
8
  :Accept-Encoding: gzip, deflate, br
9
9
  :Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
10
- :X-Client-Id: m1-web/3.20.1 #might have to update in real time
10
+ :X-Client-Id: m1-web/3.20.10 # changes regularly, hasn't broken anything yet
11
11
  :X-Client-Sentinel: <%= ((Time.new).to_i * 1000).to_s %>
12
12
 
13
13
  :authenticate:
@@ -17,8 +17,9 @@
17
17
  :Accept: application/json
18
18
  :Referer: https://dashboard.m1finance.com/login
19
19
  <<: *common_headers
20
- :body: '{"query":"\n mutation M($input: AuthenticateInput!) {\n authenticate(input: $input) {\n result {\n didSucceed\n inputError\n }\n accessToken\n refreshToken\n viewer {\n user {\n id\n correlationKey\n }\n }\n }\n }\n ","variables":{"input":{"clientMutationId":"authenticate19","username":"<<<username>>>","password":"<<<password>>>"}}}'
20
+ :body: '{"query":"mutation M($input: AuthenticateInput!) {authenticate(input: $input) {result {didSucceed inputError} accessToken refreshToken viewer {user {id correlationKey}}}} ","variables":{"input":{"clientMutationId":"authenticate19","username":"<<<username>>>","password":"<<<password>>>"}}}'
21
21
 
22
+ # lists first 1000 accounts - need to paginate if there are more
22
23
  :list_account_ids:
23
24
  :method: post
24
25
  :url: *base_url
@@ -27,7 +28,7 @@
27
28
  :Accept: '*/*'
28
29
  :Referer: https://dashboard.m1finance.com/login
29
30
  <<: *common_headers
30
- :body: '{"query":"query Active($first_0:Int!,$filterStatus_1:[AccountStatusEnum!]!) {viewer {_accounts1NFCow:accounts(first:$first_0,filterStatus:$filterStatus_1) {edges {node {id},cursor},pageInfo {hasNextPage,hasPreviousPage}},id}}","variables":{"first_0":1,"filterStatus_1":["NEW","OPENED","REJECTED"]}}'
31
+ :body: '{"query":"query Routes($first_0:Int!) {viewer {id,...F1}} fragment F0 on Account {nickname,id} fragment F1 on Viewer {profile {primary {firstName,lastName}},accounts:accounts(first:$first_0) {edges {node {id,...F0},cursor},pageInfo {hasNextPage,hasPreviousPage}},id}","variables":{"first_0":1000}}'
31
32
 
32
33
  :query_account:
33
34
  :method: post
@@ -39,27 +40,65 @@
39
40
  <<: *common_headers
40
41
  :body: '{"query":"query Routes($id_0:ID!) {node(id:$id_0) {id,__typename,...F1}} fragment F0 on Account {isRetirement,achTransferLimit,lastAchRelationship {id,nickname,__typename},balance {totalValue {value}},id} fragment F1 on Account {registration,id,...F0}","variables":{"id_0":"<<<account_id>>>"}}'
41
42
 
43
+ :query_bank_id:
44
+ :method: post
45
+ :url: *base_url
46
+ :headers:
47
+ :Accept: '*/*'
48
+ :Referer: https://dashboard.m1finance.com/d/invest/portfolio
49
+ :Authorization: Bearer <<<token>>>
50
+ <<: *common_headers
51
+ :body: '{"query":"query Routes($id_0:ID!) {node(id:$id_0) {id,__typename,...F1}} fragment F0 on Account {isLiquidating,lastAchRelationship {__typename,status,isActive,id},id} fragment F1 on Account {id,...F0}","variables":{"id_0":"<<<account_id>>>"}}'
52
+
53
+ :query_account_detail:
54
+ :method: post
55
+ :url: *base_url
56
+ :headers:
57
+ :Accept: '*/*'
58
+ :Referer: https://dashboard.m1finance.com/d/funding
59
+ :Authorization: Bearer <<<token>>>
60
+ <<: *common_headers
61
+ :body: '{"query":"query Routes($id_0:ID!,$first_1:Int!,$number_2:Int!) {node(id:$id_0) {id,__typename,...Fc}} fragment F0 on AchRelationshipViaLink {toExternalAccount {id,name,accountNumber,institutionCode},id} fragment F1 on AchRelationshipViaDeposits {id,nickname,bankAccountNumber,bankAccountType} fragment F2 on AchRelationship {__typename,id,internalId,rejectionReason,nickname,...F0,...F1} fragment F3 on AchRelationship {__typename,id,isActive} fragment F4 on AchRelationship {id,isActive,__typename} fragment F5 on Account {id,balance {cash {available}},maxCashThreshold} fragment F6 on Account {id,status,isRetirement,isLiquidating,balance {totalValue {hasValue}}} fragment F7 on Account {id,fundingTotals {totalsByYear {year,totalDeposits,totalWithdrawals}}} fragment F8 on Account {iraContributionTotals {totalsByYear {year,totalContribution,maxContribution}},id} fragment F9 on Account {isRetirement,id,...F7,...F8} fragment Fa on Account {nickname,id} fragment Fb on Account {id,registration,isRetirement,balance {margin {availableForWithdrawal}},_achTransfers:achTransfers(first:$first_1) {pageInfo {hasNextPage,hasPreviousPage},edges {node {__typename,id,direction,status,isComplete,isCreatedBySchedule,amount,contributionYear,forLiquidation,lastUpdate,viaAchRelationship {id,nickname,__typename}},cursor}},_achTransferSchedules1pTWSD:achTransferSchedules(first:$first_1) {pageInfo {hasNextPage,hasPreviousPage},edges {node {__typename,id,isDeposit,amount,description {description,_nextInstances1zNmZ6:nextInstances(number:$number_2,ofTrading:true,inTradingTimezone:true)},viaAchRelationship {id,nickname,__typename}},cursor}},...Fa} fragment Fc on Account {id,status,balance {totalValue {value}},lastAchRelationship {__typename,id,...F2,...F3,...F4},...F5,...F6,...F9,...Fb}","variables":{"id_0":"<<<account_id>>>","first_1":8,"number_2":10}}'
62
+
42
63
  :deposit:
43
- :method:
64
+ :method: post
44
65
  :url: *base_url
45
66
  :headers:
46
67
  :Accept: application/json
47
68
  :Referer: https://dashboard.m1finance.com/d/c/deposit-funds
48
69
  :Authorization: Bearer <<<token>>>
49
- :body: '{"query":"mutation CreateImmediateAchDeposit($input_0:CreateImmediateAchDepositInput!,$first_1:Int!) {createImmediateAchDeposit(input:$input_0) {clientMutationId,...F1,...F2}} fragment F0 on Account {lastAchRelationship {__typename,status,id,isActive,nickname},id,rootPortfolioSlice {id},estimatedTrading {id,hasTrades},_achTransferSchedules2RSrrY:achTransferSchedules(first:$first_1) {edges {node {id},cursor},pageInfo {hasNextPage,hasPreviousPage}}} fragment F1 on CreateImmediateAchDepositPayload {account {id,...F0}} fragment F2 on CreateImmediateAchDepositPayload {result {didSucceed,inputError}}","variables":{"input_0":{"accountId":"<<<account_id>>>","achRelationshipId":"<<<bank_id>>>","amount":"<<<deposit_amount>>>","clientMutationId":"1"},"first_1":1}}'
70
+ <<: *common_headers
71
+ :body: '{"query":"mutation CreateImmediateAchDeposit($input_0:CreateImmediateAchDepositInput!,$first_1:Int!) {createImmediateAchDeposit(input:$input_0) {clientMutationId,...F1,...F2}} fragment F0 on Account {lastAchRelationship {__typename,status,id,isActive,nickname},id,rootPortfolioSlice {id},estimatedTrading {id,hasTrades},_achTransferSchedules2RSrrY:achTransferSchedules(first:$first_1) {edges {node {id},cursor},pageInfo {hasNextPage,hasPreviousPage}}} fragment F1 on CreateImmediateAchDepositPayload {account {id,...F0}} fragment F2 on CreateImmediateAchDepositPayload {result {didSucceed,inputError}}","variables":{"input_0":{"accountId":"<<<account_id>>>","achRelationshipId":"<<<bank_id>>>","amount":"<<<amount>>>","clientMutationId":"1"},"first_1":1}}'
50
72
 
51
73
  :cancel_deposit:
52
- :method:
74
+ :method: post
53
75
  :url: *base_url
54
76
  :headers:
55
77
  :Accept: application/json
56
- :Referer: https://dashboard.m1finance.com/d/c/deposit-funds
78
+ :Referer: https://dashboard.m1finance.com/d/funding
57
79
  :Authorization: Bearer <<<token>>>
58
- :body: ''
80
+ <<: *common_headers
81
+ :body: '{"query":"mutation CancelAchTransfer($input_0:CancelAchTransferInput!,$first_1:Int!) {cancelAchTransfer(input:$input_0) {clientMutationId,...F1,...F2}} fragment F0 on Account {id,isLiquidating,_achTransfers1iwpJk:achTransfers(first:$first_1) {pageInfo {hasNextPage,hasPreviousPage},edges {node {__typename,id,direction,status,isComplete,isCreatedBySchedule,amount,contributionYear,forLiquidation,lastUpdate,viaAchRelationship {id,nickname,__typename}},cursor}}} fragment F1 on CancelAchTransferPayload {account {id,...F0}} fragment F2 on CancelAchTransferPayload {result {didSucceed,inputError}}","variables":{"input_0":{"accountId":"<<<account_id>>>","achRelationshipId":"<<<bank_id>>>","achTransferId":"<<<transfer_id>>>","clientMutationId":"2"},"first_1":8}}'
59
82
 
60
- :check_status:
61
- :method:
83
+ :withdraw:
84
+ :method: post
62
85
  :url: *base_url
86
+ :headers:
87
+ :Accept: application/json
88
+ :Referer: https://dashboard.m1finance.com/d/c/withdraw-funds/confirm
89
+ :Authorization: Bearer <<<token>>>
90
+ <<: *common_headers
91
+ :body: '{"query":"mutation CreateImmediateAchWithdrawal($input_0:CreateImmediateAchWithdrawalInput!) {createImmediateAchWithdrawal(input:$input_0) {clientMutationId,...F1,...F2}} fragment F0 on Account {id} fragment F1 on CreateImmediateAchWithdrawalPayload {achTransferEdge {cursor,__typename,node {__typename,id,direction,status,isComplete,isCreatedBySchedule,amount,contributionYear,forLiquidation,lastUpdate,viaAchRelationship {id,nickname,__typename}}},account {id,...F0}} fragment F2 on CreateImmediateAchWithdrawalPayload {result {didSucceed,inputError}}","variables":{"input_0":{"accountId":"<<<account_id>>>","achRelationshipId":"<<<bank_id>>>","amount":"<<<amount>>>","clientMutationId":"3"}}}'
92
+
93
+ :cancel_withdraw:
94
+ :method: post
95
+ :url: *base_url
96
+ :headers:
97
+ :Accept: application/json
98
+ :Referer: https://dashboard.m1finance.com/d/funding
99
+ :Authorization: Bearer <<<token>>>
100
+ <<: *common_headers
101
+ :body: '{"query":"mutation CancelAchTransfer($input_0:CancelAchTransferInput!,$first_1:Int!) {cancelAchTransfer(input:$input_0) {clientMutationId,...F1,...F2}} fragment F0 on Account {id,isLiquidating,_achTransfers1iwpJk:achTransfers(first:$first_1) {pageInfo {hasNextPage,hasPreviousPage},edges {node {__typename,id,direction,status,isComplete,isCreatedBySchedule,amount,contributionYear,forLiquidation,lastUpdate,viaAchRelationship {id,nickname,__typename}},cursor}}} fragment F1 on CancelAchTransferPayload {account {id,...F0}} fragment F2 on CancelAchTransferPayload {result {didSucceed,inputError}}","variables":{"input_0":{"accountId":"<<<account_id>>>","achRelationshipId":"<<<bank_id>>>","achTransferId":"<<<transfer_id>>>","clientMutationId":"4"},"first_1":8}}'
63
102
 
64
103
  :test_get:
65
104
  :method: get
@@ -0,0 +1,46 @@
1
+ require 'json'
2
+
3
+ # need some semi automated way of maintaining apis as they change
4
+ # start selenium web session, authenticate manually, then run scripts to go through flows
5
+ # parse the generated .har file and compare against current api configs file
6
+ # might have to do fuzzy matching of the schema?
7
+
8
+ # create git conflict for each call list as an accept/reject
9
+
10
+ # to do the replace string, should convert this to a class
11
+
12
+ module MaintenanceHelpers
13
+ module_function
14
+
15
+ def load_har(path_to_file)
16
+ out = []
17
+ JSON.parse(File.read(path_to_file))['log']['entries'].each do |call|
18
+ out << {
19
+ start_time: call['startTime'],
20
+ request: {
21
+ method: call['request']['method'],
22
+ url: call['request']['url'],
23
+ headers: call['request']['headers'],
24
+ body: call['request']['postData']
25
+ },
26
+ response: {
27
+ status: call['response']['status'],
28
+ content: call['response']['content']
29
+ }
30
+ }
31
+ end
32
+ out
33
+ end
34
+
35
+ def filter_by_top_value(calls, key, value)
36
+ if value.is_a? Regexp
37
+ calls.select { |call| call if call[:request][key].match?(value) }
38
+ else
39
+ calls.select { |call| call if call[:request][key].include?(value) }
40
+ end
41
+ end
42
+
43
+ def replace_value_with_key(data, matcher, key)
44
+
45
+ end
46
+ end
@@ -1,3 +1,3 @@
1
1
  class M1API
2
- VERSION = Version = '0.0.4'
2
+ VERSION = Version = '0.1.0'
3
3
  end
@@ -35,7 +35,13 @@ module YamlHelpers
35
35
  raise 'input is not a array' unless array.is_a?(Array)
36
36
  dup = array.clone
37
37
  dup.each_with_index do |value, index|
38
- dup[index] = replace_dynamic_string(value, context)
38
+ if value.is_a?(String)
39
+ dup[index] = replace_dynamic_string(value, context)
40
+ elsif value.is_a?(Array)
41
+ dup[index] = replace_dynamic_array(value, context)
42
+ elsif value.is_a?(Hash)
43
+ dup[index] = replace_dynamic_hash(value, context)
44
+ end
39
45
  end
40
46
  dup.join
41
47
  end
@@ -54,8 +60,8 @@ module YamlHelpers
54
60
  hash
55
61
  end
56
62
 
57
- def call_api_from_yml(config_file, api, data = {})
58
- config = load_yaml(config_file)[api.to_sym]
63
+ def call_api_from_config(configs, api, data = {})
64
+ config = configs[api].dup
59
65
  raise "no api defined for #{api}" unless config
60
66
  context = config.merge data
61
67
  parsed_config = replace_dynamic_hash(context)
@@ -67,4 +73,9 @@ module YamlHelpers
67
73
  return { code: res.code, body: res.body } if res
68
74
  puts "failed to call api for api #{api}: #{e}"
69
75
  end
76
+
77
+ def call_api_from_yml(config_file, api, data = {})
78
+ configs = load_yaml(config_file)
79
+ call_api_from_config(configs, api, data)
80
+ end
70
81
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: m1_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuan Feng
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-06 00:00:00.000000000 Z
11
+ date: 2019-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -47,6 +47,7 @@ files:
47
47
  - Readme.md
48
48
  - lib/m1_api.rb
49
49
  - lib/m1_api/api_configs.yml
50
+ - lib/m1_api/maintenance_helpers.rb
50
51
  - lib/m1_api/version.rb
51
52
  - lib/m1_api/yaml_helpers.rb
52
53
  homepage: http://github.com/ynotgnef/m1_api