Commit fb3c9fb4 authored by Jose Ernesto Suarez's avatar Jose Ernesto Suarez

Primera version todavia no pasa los tests de configuracion

parent ccacf591
.DS_Store
TO-DO
\ No newline at end of file
* @jernestosuarez
\ No newline at end of file
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at ernesto@wedoops.io. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
FROM ruby:2.7
WORKDIR /app
ADD Gemfile /app/Gemfile
#ADD Gemfile.lock /app/Gemfile.lock
ADD . /app
RUN bundle install --system
RUN rake spec
RUN gem build zoholib.gemspec
#TO-DO: Get automatically the version number
RUN gem install zoholib-$(cat /app/lib/zoholib/version.rb|grep VERSION|cut -d '=' -f 2|sed "s/'//g" | sed "s/ //g").gem
CMD ["ruby", "main.rb"]
\ No newline at end of file
# frozen_string_literal: true
source 'https://rubygems.org'
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
## Specify your gem's dependencies in zoholib.gemspec
gemspec
gem 'httparty','~> 0.18.0'
gem 'http','~> 4.4.0'
gem 'logger','~> 1.4.2'
gem 'savon','~> 2.12.0'
gem 'sib-api-v3-sdk','~> 5.3.0'
gem 'launchy','~> 2.5.0'
\ No newline at end of file
PATH
remote: .
specs:
zoholib (0.0.1b)
GEM
remote: https://rubygems.org/
specs:
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
akami (1.3.1)
gyoku (>= 0.4.0)
nokogiri
builder (3.2.4)
diff-lcs (1.3)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
ethon (0.12.0)
ffi (>= 1.3.0)
ffi (1.12.2)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
gyoku (1.3.1)
builder (>= 2.1.2)
http (4.4.1)
addressable (~> 2.3)
http-cookie (~> 1.0)
http-form_data (~> 2.2)
http-parser (~> 1.2.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
http-form_data (2.3.0)
http-parser (1.2.1)
ffi-compiler (>= 1.0, < 2.0)
httparty (0.18.0)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpi (2.4.4)
rack
socksify
json (2.3.0)
launchy (2.5.0)
addressable (~> 2.7)
logger (1.4.2)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009)
mini_portile2 (2.4.0)
multi_xml (0.6.0)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
nori (2.6.0)
public_suffix (4.0.4)
rack (2.2.2)
rake (13.0.1)
rspec (3.9.0)
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-core (3.9.1)
rspec-support (~> 3.9.1)
rspec-expectations (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-support (3.9.2)
savon (2.12.0)
akami (~> 1.2)
builder (>= 2.1.2)
gyoku (~> 1.2)
httpi (~> 2.3)
nokogiri (>= 1.8.1)
nori (~> 2.4)
wasabi (~> 3.4)
sib-api-v3-sdk (5.3.0)
json (~> 2.1, >= 2.1.0)
typhoeus (~> 1.0, >= 1.0.1)
socksify (1.7.1)
typhoeus (1.3.1)
ethon (>= 0.9.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
wasabi (3.5.0)
httpi (~> 2.0)
nokogiri (>= 1.4.2)
PLATFORMS
ruby
DEPENDENCIES
bundler (~> 1.15)
http (~> 4.4.0)
httparty (~> 0.18.0)
launchy (~> 2.5.0)
logger (~> 1.4.2)
rake (~> 13.0.1)
rspec (~> 3.0)
savon (~> 2.12.0)
sib-api-v3-sdk (~> 5.3.0)
zoholib!
BUNDLED WITH
1.16.4
The MIT License (MIT)
Copyright (c) 2019 ChaingoTech SL
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# zoholib # Zoho Library
\ No newline at end of file
# frozen_string_literal: true
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task default: :spec
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'bundler/setup'
require 'zoholib'
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require 'irb'
IRB.start(__FILE__)
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
# frozen_string_literal: true
require 'zoholib/configuration'
require 'zoholib/version'
require 'zoholib/zoho_request'
require 'zoholib/response'
require 'zoholib/grant_request'
require 'zoholib/refresh_request'
require 'zoholib/authorization_request'
require 'zoholib/adamo_client'
require 'zoholib/b12_client'
require 'zoholib/zoho_basic_client'
require 'zoholib/zoho_client'
##
# This library is used for access Zoho
module Zoholib
class << self
attr_accessor :configuration
end
def self.configuration
puts "Init connfig"
@configuration ||= Configuration.new
end
def self.reset
@configuration = Configuration.new
end
def self.configure
puts "configuring"
yield(configuration)
end
end
require 'http'
require 'pp'
require 'json'
require 'logger'
require 'date'
module Zoholib
class AdamoClient
def initialize(options={})
@logger = Logger.new(STDOUT)
@http = HTTP
.use(logging: {logger: @logger})
.timeout(connect: 15, read: 30)
.headers("cache-control" => "no-cache",
"accept" => "application/json",
"postman-token" => "8b126eb1-0de6-4a10-d78d-1fb417fb23b0",
"x-apikey" => "Af8cbSpgkAReYqBs66DHJUkf2MJW3d9JDFVJhWe4U5haD")
end
def cobertura_project
count=2
url = "https://coverage-dump.adamo.es/v1/cobertura-project/#{count}?limit=500"
return HTTP.use(logging: {logger: @logger})
.timeout(connect: 15, read: 30)
.headers("cache-control" => "no-cache",
"accept" => "application/json",
"postman-token" => "8b126eb1-0de6-4a10-d78d-1fb417fb23b0",
"x-apikey" => "Af8cbSpgkAReYqBs66DHJUkf2MJW3d9JDFVJhWe4U5haD").get(url)
end
def cobertura
page=1
limit=50000
#date=Date.parse("06-04-2020").strftime("%Y%m%d")
date=Date.today.prev_day.strftime("%Y%m%d")
resultado_final=Array.new
r=Array.new
loop do
print "#{page} "
url= "https://coverage-dump.adamo.es/v1/cobertura/#{page}?limit=#{limit}&date=#{date}"
#.use(logging: {logger: @logger})
resultado= HTTP.timeout(connect: 15, read: 30)
.headers("cache-control" => "no-cache",
"accept" => "application/json",
"postman-token" => "8b126eb1-0de6-4a10-d78d-1fb417fb23b0",
"x-apikey" => "zYkWN38SYH6hr2Ixq75xvDENl6hrYOvxx0FxLvPD7BUe6").get(url)
r= JSON.parse(resultado, symbolize_names: true)
resultado_final.concat(r)
page+=1
print "(#{r.size}/#{resultado_final.size}),"
break if r.size < limit || page >2
end
puts resultado_final.size
return resultado_final
end
def projects
page=1
limit=50000
#date=Date.today.prev_day.strftime("%Y%m%d")
date=Date.parse("01-01-2011").strftime("%Y%m%d")
resultado_final=Array.new
r=Array.new
loop do
print "#{page} "
url= "https://coverage-dump.adamo.es/v1/cobertura-project/#{page}?limit=#{limit}&date=#{date}"
#.use(logging: {logger: @logger})
resultado= HTTP.timeout(connect: 15, read: 30)
.headers("cache-control" => "no-cache",
"accept" => "application/json",
"postman-token" => "8b126eb1-0de6-4a10-d78d-1fb417fb23b0",
"x-apikey" => "zYkWN38SYH6hr2Ixq75xvDENl6hrYOvxx0FxLvPD7BUe6").get(url)
r= JSON.parse(resultado, symbolize_names: true)
resultado_final.concat(r)
page+=1
print "(#{r.size}/#{resultado_final.size}),"
break if r.size < limit || page >2
end
puts resultado_final.size
return resultado_final
end
private
def get_params(url,payload)
@http.get(url, :params => payload)
end
def get(url)
@http.get(url)
end
end
end
\ No newline at end of file
#require 'zoho_request'
#require './lib/grant_request'
#En algun momento tenemos que abrir un navegador
require 'launchy'
require 'socket'
module Zoholib
class AuthorizationRequest < Zoholib::ZohoRequest
# Sin en algun momento se quiere evitar el encode de URL
# he aqui un truco
# En esta ocasion lo gastamos porque los scopes vienen separados por una coma que hace que falle
query_string_normalizer proc { |query|
query.map do |key, value|
[value].flatten.map {|v| "#{key}=#{v}"}.join('&')
end.join('&')
}
def initialize(options={})
#build_url(options)
@logger = ::Logger.new(STDOUT)
@options = Hash.new
options.merge!({response_type: 'code',access_type: 'offline',redirect_uri: 'http://localhost:8000/auth/callback'})
@grant_token = options[:grant_token]
#@options.merge!(grant_token: options[:grant_token] )
options.delete(:grant_token)
#Al iniciar este objeto estas opciones solo tienen sentido en query
@options.merge!(query: options)
@logged_in = false
end
def authorize(options={})
unless @options[:refresh_token].nil?
@logger.warn "Refresh token already exists! Ignoring call"
return @options
end
#El prompt consent obliga a mostrar la pantalla de consent
# de esta manera se consigue que devuelva siempre el
# refresh token que es valido para siempre
if options.has_key?(:force_prompt)
#Si esta opcion la establecemos a true entonces se forzara el consent
@options[:query].merge!(prompt:"consent") if options[:force_prompt]
end
#pp @options
get_grant_token if @grant_token.nil?
tokens = get_grant_request
#pp tokens
raise "Unknown error! I cannot find the refresh_token! try to force prompt" if tokens[:refresh_token].nil?
@options.merge!(tokens)
# Decido devolver tokens unicamente, dejo aqui los comentarios antiguos
# #Ya no necesitamos estas opciones dentro de query, las sacamos fuera
# # Para devolverselas a nuestro padre
# @options.merge!(@options[:query])
# @options.delete(:query)
# # El code solo tiene un uso y caducal al minuto, asi que
# # Lo eliminamos tambien
# @options.delete(:code)
@options.delete(:follow_redirects)
return tokens
end
def read_socket(port=8000)
@logger.info "Reading socket response"
server = TCPServer.new port
while session = server.accept
request = session.gets
match = /code=\d{4}\.\w{32}\.\w{32}/.match(request)
if match
code = match.to_s.split("=").last
end
session.print "HTTP/1.1 200\r\n" # 1
session.print "Content-Type: text/html\r\n" # 2
session.print "\r\n" # 3
session.print "Thank you! The time is #{Time.now}" #4
session.close
break
end
server.close
return code
end
def get_grant_token
# Ejecuta lo descrito en el paso 2 del ZOHO OAUTH https://www.zoho.com/crm/developer/docs/api/auth-request.html
# Para recibir el codigo que nos permitira generar los tokens
# Atencion, este metodo abre un navegador para autorizar y un socket en el puerto 8000 para recibir el codigo tras
# la aceptacion de permisos
@options.merge!(follow_redirects: false)
response=self.class.get('/auth', @options)
# puts "RESPONSE CODE:#{response.code}"
if response.code >= 300 && response.code < 400
redirect_url = response.headers['location']
Launchy.open(redirect_url)
grant_token=read_socket
@logger.info "GRANT TOKEN: #{grant_token}"
@grant_token = grant_token
return true
end
raise "Unable to get the grant token!"
end
def get_grant_request
# Prepara la llamada descrita en el paso 3 del ZOHO OAUTH https://www.zoho.com/crm/developer/docs/api/access-refresh.html
# Para recibir access_token y refresh_token
raise "Unable to get a grant request without grant token!" if @grant_token.nil?
options_grant_request = @options.dup
options_grant_request.delete(:grant_token)
options_grant_request.delete(:follow_redirects)
options_grant_request[:query].delete(:response_type)
options_grant_request[:query].delete(:scope)
options_grant_request[:query].delete(:access_type)
options_grant_request[:query].merge!(code: @grant_token)
grant_request=Zoholib::GrantRequest.new(options_grant_request)
result=grant_request.token
# pp result
raise "Error #{result[:error]}" unless result[:error].nil?
return result
end
end
end
\ No newline at end of file
require 'http'
require 'pp'
require 'json'
require 'logger'
require 'date'
require 'savon'
module Zoholib
class B12Client
def initialize(options={})
@logger = Logger.new(STDOUT)
initialize_client
@client.call(:lead)
end
def send_lead(lead)
@logger.info("Sending lead: #{lead[:idvar]} tlf:#{lead[:Telefono]}")
#pp lead
#puts ":.................:"
begin
response = @client.call(:lead) do
message(lead)
end
rescue Exception => e
@logger.error("Error sending the lead: #{e.message}")
return nil
end
begin
b12_id = response.body[:lead_response][:return]
unless b12_id.match(/^\d*$/)
raise "Error de B12, envio #{lead[:idvar]} tlf:#{lead[:Telefono]} duplicado (#{b12_id})" if b12_id == "-1"
raise "Error interno de B12, error #{b12_id} recibido"
end
rescue Exception => e
@logger.error("Error getting the LEAD ID: #{e.message}")
return nil
end
return b12_id
end
private
def initialize_client
@client = Savon.client do
wsdl 'http://panel.digitalion.com/wss/cobalt.lead.php?wsdl'
pretty_print_xml true
#convert_request_keys_to :camelcase
log false
log_level :info
end
end
end
end
\ No newline at end of file
# frozen_string_literal: true
module Zoholib
##
# This class permits the configuration of the Zoholib
class Configuration
attr_accessor :client_id, :client_secret, :scope, :refresh_token
def initialize
@client_id = nil
@client_secret = nil
@scope = nil
@refresh_token = nil
end
end
end
#require './lib/zoho_request'
module Zoholib
class GrantRequest < Zoholib::ZohoRequest
#base_uri "https://accounts.zoho.com/oauth/v2/token"
#headers: {"Authorization" => "Token token=\"111\""}
def initialize(options={})
@logger = ::Logger.new(STDOUT)
@logged_in = false
@options = options
end
def token(options={})
# Esta funcion se llama desde Zoholib::AuthorizationRequest
# Ejecuta lo descrito en https://www.zoho.com/crm/developer/docs/api/access-refresh.html
@options[:query].merge!(grant_type: "authorization_code")
@options.merge!(:logger => @logger)
response=self.class.post('/token',@options)
JSON.parse(response.body,symbolize_names: true)
end
end
end
\ No newline at end of file
#require './lib/zoho_request'
module Zoholib
class RefreshRequest < Zoholib::ZohoRequest
# Sin en algun momento se quiere evitar el encode de URL
# he aqui un truco
#query_string_normalizer proc { |query|
# query.map do |key, value|
# [value].flatten.map {|v| "#{key}=#{v}"}.join('&')
# end.join('&')
# }
def initialize(options={})
options.merge!(grant_type: "refresh_token")
@options={:query => options}
end
def get_token
self.class.post("/token",@options)
end
end
end
\ No newline at end of file
module Zoholib
module Zoho
class Response
attr_accessor :body, :status
def initialize(output = nil)
# puts "Response initialized"
@body=nil
@status=nil
@output=nil
begin
# No parseo con symbolize porque se queda al a partir de la tercera capa
raw=parse(output)
rescue Exception => e
puts "_______ ERROR PARSE_____"
puts e.backtrace
raise "#{__method__} Error parsing JSON: #{e.message}"
end
return @body
end
def success?
@status[:execution_code] == "success"
end
def id
@status[:id]
end
private
def valid_json?(string)
!!JSON.parse(string)
rescue JSON::ParserError
false
end
def standarize_collection(cadena)
# Aqui deberia controlar cuando es array y cuando elemento o devolver siempre un array de elementos
#puts "Standarizing this"
# puts "_------------------- INIT"
# puts cadena
#puts "_------------------- END"
if valid_json?("[#{cadena}]")
@output = Array
"[#{cadena}]"
else
@output = String
cadena
end
end
def parse(string,options = nil)
# For parsing JSON
begin
raw = JSON.parse(string,options)
rescue Exception => e
puts "#{__method__} Error del parser #{e.message} "
end
begin
generate_metadata(raw)
resultado = raw
rescue Exception => e
puts "_______ ERROR METADATAAA"
puts e.backtrace
puts "#{__method__} Error generando metadata: #{e.message} "
end
#raise "El parseo de JSON ha generado un resultado invalido!" unless valid_json?(resultado)
serializar_output(raw)
return @body
end
def generate_metadata(raw)
puts "Generating metadata!"
# Informacion de la llamada
@execution_code="#{raw["code"]}"
puts "Execution code #{@execution_code}"
@output_type="#{raw["details"]["output_type"]}"
puts "OutputType #{@output_type}"
@id="#{raw["details"]["id"]}"
puts "ID #{@id}"
@message="#{raw["message"]}"
puts "Message #{@message}"
@status = {
:execution_code => @execution_code,
:id => @id,
:output_type => @output_type,
:message => @message
}
puts "Initialized with:"
return @status
end
def serializar_output(raw)
resultado=standarize_collection(raw["details"]["output"])
begin
if @output == Array
output_serializado=JSON.parse(resultado,:symbolize_names => true)
else
output_serializado = {body: resultado}
end
rescue Exception => e
raise "#{__method__} Error serilizando la respuesta!: #{e.message}"
end
####
#Compruebo que a la salida doy un Hash valido pasandolo a JSON y validandolo
raise "El parseo de JSON ha generado un resultado invalido!" unless valid_json?(output_serializado.to_json)
@body = output_serializado
end
end
end
end
\ No newline at end of file
# frozen_string_literal: true
module Zoholib
VERSION = '0.0.1b'
end
require 'pp'
require 'json'
require 'logger'
#require 'date'
require 'httparty'
#require './lib/refresh_request'
#require './lib/authorization_request'
module Zoholib
class ZohoBasicClient
include HTTParty
base_uri 'https://www.zohoapis.com/crm/v2/'
def initialize(options={})
#Es un cliennte basico que trabaja a partir de refresh_tokens
@logger = ::Logger.new(STDOUT)
@logged_in = false
@options = options
@token = nil
end
def login
unless @options.has_key?(:refresh_token)
#En estos casos necesito mi refresh_token primero
auth_request=Zoholib::AuthorizationRequest.new(@options)
# Ojo esta opcion fuerza que siempre salga la pantalla
# de credenciales
result=auth_request.authorize({force_prompt: true})
@options.merge!(result)
#El refresh token habria que persisitirlo para
#evitar tener que cogerlo de nuevo
end
request= Zoholib::RefreshRequest.new(@options)
response=request.get_token
@logger.debug("#{__method__}:#{response.request.last_uri.to_s}")
response = JSON.parse(response.to_s, symbolize_names: true)
unless response[:access_token].nil?
@logged_in = true
@token = response[:access_token]
else
@logged_in = false
@token = nil
end
return @logged_in
end
def read_all(url,params={})
# ojo, maxrecords no tiene en cuenta cuantos registros se descaargan
# en cada ocasion por lo que puede devolver registros de mas
# como resto de la llamada anterior
page=1
maxrecords=-1
result = Hash.new
result[:data] = Array.new
params.delete(:page) if params.has_key?(:page)
if params.has_key?(:max_records)
maxrecords = params[:max_records]
params.delete(:max_records)
end
loop do
params.merge!(page: page)
response=read(url,params)
if response[:info][:more_records] == true
page+=1
result[:data].concat(response[:data])
break if maxrecords > 0 && result[:data].size >= maxrecords
else
break
end
end
return result
end
def read(url,params=nil)
self.login unless @logged_in
options = {}
options[:query] = params unless params.nil?
options.merge!(build_header)
response=self.class.get("/#{url}",options)
@logger.debug("#{__method__}:#{response.request.last_uri.to_s}")
response = JSON.parse(response.to_s, symbolize_names: true)
return response
end
def write(url,data)
raise "Invalid JSON!" unless valid_json?(data)
self.login unless @logged_in
options = {}
#Ojo, aqui tal vez deberiamos coprobar que viene con la forma :data => [:coleccion]
options[:body]=data
options.merge!(build_header)
response=self.class.post("/#{url}",options)
@logger.debug("#{__method__}:#{response.request.last_uri.to_s}")
response = JSON.parse(response.to_s, symbolize_names: true)
end
def update(url,data)
raise "Invalid JSON!" unless valid_json?(data)
self.login unless @logged_in
options = {}
#Ojo, aqui tal vez deberiamos coprobar que viene con la forma :data => [:coleccion]
options[:body]=data
options.merge!(build_header)
response=self.class.put("/#{url}",options)
@logger.debug("#{__method__}:#{response.request.last_uri.to_s}")
response = JSON.parse(response.to_s, symbolize_names: true)
end
def query(coql)
self.login unless @logged_in
options = {}
#Ojo, aqui tal vez deberiamos coprobar que viene con la forma :data => [:coleccion]
options[:body] = {:select_query => coql}.to_json
# options[:query]={scope:"ZohoCRM.coql.READ,ZohoCRM.modules.all"}
options[:logger]=@logger
options.merge!(build_header)
response=self.class.post("/coql",options)
#response=self.class.post("/coql?scope=ZohoCRM.modules.ALL,ZohoCRM.coql.read",options)
@logger.debug("#{__method__}:#{response.request.last_uri.to_s}")
response = JSON.parse(response.to_s, symbolize_names: true)
end
def query_all(coql,params={})
# ojo, maxrecords no tiene en cuenta cuantos registros se descaargan
# en cada ocasion por lo que puede devolver registros de mas
# como resto de la llamada anterior
page=1
maxrecords=-1
result = Hash.new
result[:data] = Array.new
params.delete(:page) if params.has_key?(:page)
if params.has_key?(:max_records)
maxrecords = params[:max_records]
params.delete(:max_records)
end
loop do
params.merge!(page: page)
response=self.query(coql)
return response if response[:status] == "error"
if response[:info][:more_records] == true
page+=1
result[:data].concat(response[:data])
break if maxrecords > 0 && result[:data].size >= maxrecords
else
break
end
@logger.debug("#{result[:data].size} records")
@logger.debug("#{result[:data].last} ")
end
return result
end
private
def build_header
{:headers => { "Authorization" => "Zoho-oauthtoken #{@token}"}}
end
def valid_json?(json)
JSON.parse(json)
return true
rescue JSON::ParserError => e
return false
end
end
end
require 'http'
require 'pp'
require "erb"
require 'json'
require 'logger'
require 'date'
require 'httparty'
#include ERB::Util
module Zoholib
class ZohoClient < ERB
attr_accessor :token, :scope, :auth_type, :client_id, :redirect_uri, :code
def initialize(options={})
@auth_type=nil
@user=nil
@pass=nil
@scope=nil
@life_token=nil
@client_id=nil
@client_secret=nil
@code=nil
@grant_type=nil
@access_token=nil
@refresh_token=nil
@api_domain=nil
@toke_type=nil
@expires_in=nil
@zapikey=nil
@redirect_uri=nil
@logger = Logger.new(STDOUT)
raise "Error constructing the class!" unless initialize_options(options)
@http = HTTP
.use(logging: {logger: @logger})
.timeout(connect: 15, read: 30)
.headers("cache-control" => "no-cache")
# .accept(:json)
@logger.info("Initialized")
end
def get_code
url="https://accounts.zoho.com/oauth/v2/auth?scope=#{@scope}&client_id=#{@client_id}&response_type=code&access_type=offline&redirect_uri=#{@redirect_uri}"
@logger.info(url)
@logger.info("#> Sending request....")
HTTP.follow
.use(logging: {logger: @logger})
.timeout(connect: 15, read: 30)
.headers("cache-control" => "no-cache")
.get(url)
end
def token
#Dependiendo del tipo de autenticacion devolvere un tipo de token
case check_mode
when "apikey"
return @zapikey
when "oauth"
return @access_token
when "auth-token"
return @life_token
else
raise "Seems uninitialized!"
end
return nil
end
def token?
valid_token?
end
def grant_token!(token)
#Actualizo el codigo del grant token desde un callback de oauth probablemente
raise "Invalid token #{token}" unless /^\w{4}.\w{32}.\w{32}$/.match(token)
@logger.info("Grant Token updated! #{token}")
@code = token
get_refresh_token
persist_token
true
end
def refresh_token!
raise "Unsupported mode" unless check_mode == "oauth"
#if @refresh_token.nil?
# @logger.info("@refresh_token nulo, vamos a actualizar")
# get_refresh_token
# @logger.info("Actualizado el refresh token! ->#{@refresh_token}<-")
#end
#En este supuesto acabo de inicializar con los basico de Oauth
# y quiero traerme la informacion con @code de un fichero
@logger.info("------------> Carga 1")
load_previous_token if token.nil?
@logger.info("TOKEN: #{token}")
@logger.info("auth_token: #{@auth_token}")
@logger.info("CODE: #{@code}")
show_current_options
@logger.info "Show VALID TOKEM DIRA:"
if token?
@logger.info("DICE QUE GUAY")
else
@logger.info("DICE QUE NO")
#get_refresh_token
# refresh_oauth_token
end
if token.nil?
@logger.info("TOken sigue siendo nulo")
if @code.nil?
@logger.info("------------> Carga 2!!!!")
#Tal vez habria que implementar un mecanismo para traer nuevos codes
raise "Unable to load it!!!" unless load_previous_token
end
# Si tengo un code debe ser que aun no se ha gastado
if get_authorization_token
@logger.info("Token refrescado #{@access_token}")
@http.headers("Authorization" => "Zoho-oauthtoken #{token}")
# Al persistirlo estamos grabando el refresh_token
#persist_token
#A la salida lo dejamos en blanco ya que no va a ser valido
#@refresh_token = nil
persist_token
return true
else
@logger.error("No ha sido posible refrescar el token")
return false
end
else
@logger.info("NO se refresca el token dado que todavia lo tenemos! #{token} : )")
true
end
end
def renew_life_token!(force=false)
raise "Unsupported mode" unless check_mode == "auth-token"
update_life_token!(force)
end
def execute(function,params=nil)
salida = case function
when "holamundo"
execute_function("https://www.zohoapis.com/crm/v2/functions/holamundo/actions/execute?auth_type=apikey&zapikey=1003.7fb3bae78b241ff68541181c3b699cf1.983d91325ef11808acdc31d225fdca60")
when "holamundo2"
execute_function("https://www.zohoapis.com/crm/v2/functions/holamundo/actions/execute",{
auth_type: "apikey",
zapikey: "1003.7fb3bae78b241ff68541181c3b699cf1.983d91325ef11808acdc31d225fdca60"})
# ?auth_type=apikey&zapikey=1003.7fb3bae78b241ff68541181c3b699cf1.983d91325ef11808acdc31d225fdca60")
when "getJSON"
execute_function("https://www.zohoapis.com/crm/v2/functions/getjson/actions/execute?auth_type=apikey&zapikey=1003.7fb3bae78b241ff68541181c3b699cf1.983d91325ef11808acdc31d225fdca60")
when "getJSONParams"
execute_function("https://www.zohoapis.com/crm/v2/functions/getjsonparams/actions/execute?auth_type=apikey&zapikey=1003.7fb3bae78b241ff68541181c3b699cf1.983d91325ef11808acdc31d225fdca60",params)
when "call"
#just standard function
#https://crm.zoho.com/crm/v2/functions/{api_name_of_function}/actions/execute?auth_type=apikey&zapikey={zapikey}
execute_function("https://crm.zoho.com/crm/v2/functions/{api_name_of_function}/actions/execute?auth_type=apikey&zapikey=1003.7fb3bae78b241ff68541181c3b699cf1.983d91325ef11808acdc31d225fdca60")
when "read_api"
execute_function("https://www.zohoapis.com/crm/v2/Modules/")
when "adamo_address"
result=Array.new
(0..4).each do |page|
puts "Zoho-oauthtoken #{token}"
url="https://www.zohoapis.com/crm/v2/Adamo_Addresses?page=#{page}&sort_by=Adamo_ID"
#temp= HTTP.use(logging: {logger: @logger}).headers("Authorization" => "Zoho-oauthtoken #{token}").get(url)
temp=HTTParty.get(url,format: :json,:headers => {"Authorization" => "Zoho-oauthtoken #{token}"})
puts "......."
pp temp.body
puts "......"
result << temp
end
return result
when "get_settings"
url= "https://www.zohoapis.com/crm/v2/settings/modules"
params={:scope => @scope}
return get_params(url,params)
else
puts "Unknown function"
end
return normalize_output(salida)
end
def execute_oauth(function)
url="https://www.zohoapis.com/crm/v2/functions/holamundo/actions/execute?auth_type=oauth"
end
def valid_json?(string)
!!JSON.parse(string)
rescue JSON::ParserError
false
end
private
def execute_function(url,params=nil)
# Ejecutamos una funcion GET con los parametors dados
# payload = {
# limit: 20,
# authtoken: @life_token,
# scope: "creatorapi",
# }
execute_url = url
payload = params
get_params(execute_url, params)
end
def get_refresh_token
@logger.info("#{__method__} con @refresh_token = #{@refresh_token} y grant_token (@code) = #{@code}")
#Proceso OAUTH para recibir un nuevo refresh_token para poder refrescar el token
#url="https://accounts.zoho.com/oauth/v2/token?client_id=#{@client_id}&client_secret=#{@client_secret}&grant_type=authorization_code&code=#{@code}"
#url="https://accounts.zoho.com/oauth/v2/token?client_id=#{@client_id}&client_secret=#{@client_secret}&grant_type=refresh_token&code=#{@code}&redirect_uri=http://localhost:8000/auth/callback"
url="https://accounts.zoho.com/oauth/v2/token?client_id=#{@client_id}&client_secret=#{@client_secret}&grant_type=authorization_code&code=#{@code}&redirect_uri=#{@redirect_uri}"
res=post(url)
resultado = JSON.parse(res,:symbolize_names => true)
if resultado.has_key?(:error)
#pp resultado
@logger.error("Se ha detectado un error solicitando el refresh token. Arakiri.")
raise resultado[:error]
end
puts "-------RESULTADO DE GET REFRESH TOKEN----"
pp resultado
puts "..........."
@refresh_token = resultado[:refresh_token]
@token_type = resultado[:token_type]
@api_domain = resultado[:api_domain]
@access_token = resultado[:access_token]
@expires_in = (Time.new + resultado[:expires_in]).to_datetime
persist_token
return true
end
def get_authorization_token
@logger.info("#{__method__} con @refresh_token = #{@refresh_token} y grant_token (@code) = #{@code}")
#Proceso OAUTH para recibir un nuevo refresh_token para poder refrescar el token
#url="https://accounts.zoho.com/oauth/v2/token?client_id=#{@client_id}&client_secret=#{@client_secret}&grant_type=authorization_code&code=#{@code}"
#url="https://accounts.zoho.com/oauth/v2/token?client_id=#{@client_id}&client_secret=#{@client_secret}&grant_type=refresh_token&code=#{@code}&redirect_uri=http://localhost:8000/auth/callback"
url="https://accounts.zoho.com/oauth/v2/token?client_id=#{@client_id}&client_secret=#{@client_secret}&grant_type=authorization_code&code=#{@code}&redirect_uri=#{@redirect_uri}"
res=post(url)
resultado = JSON.parse(res,:symbolize_names => true)
if resultado.has_key?(:error)
#pp resultado
@logger.error("Se ha detectado un error solicitando el authorization token. Arakiri.")
raise resultado[:error]
end
puts "-------RESULTADO DE GET AUTHORIZATION TOKEN----"
pp resultado
puts "..........."
@token_type = resultado[:token_type]
@api_domain = resultado[:api_domain]
@access_token = resultado[:access_token]
@expires_in = (Time.new + resultado[:expires_in]).to_datetime
# El code lo tengo que invalidar porque ya no es valido
@code = nil
persist_token
return true
end
def refresh_oauth_token
#params=auth_params("refresh_token")
@logger.info("Refrescando el token....")
url="https://accounts.zoho.com/oauth/v2/token?refresh_token=#{@refresh_token}&client_id=#{@client_id}&client_secret=#{@client_secret}&grant_type=refresh_token"
@logger.info("URL REFRESCO: #{url}")
# Pruebo sin pasar el secret
#url="https://accounts.zoho.com/oauth/v2/token?refresh_token=#{@refresh_token}&client_id=#{@client_id}&grant_type=refresh_token"
res=post(url)
resultado = JSON.parse(res,:symbolize_names => true)
if resultado.has_key?(:error)
pp resultado
@logger.error("Se ha detectado un error al refrescar el token.")
raise resultado[:error]
end
puts "-----------"
pp resultado
puts "..........."
@access_token = resultado[:access_token]
@token_type = resultado[:token_type]
@api_domain = resultado[:api_domain]
@expires_in = (Time.new + resultado[:expires_in]).to_datetime
return true
end
def update_life_token!(force=false)
#Recogemos el token para toda la vida
token_url="https://accounts.zoho.com/apiauthtoken/xml/create?SCOPE=#{@scope}&EMAIL_ID=#{@user}&PASSWORD=#{@pass}"
#A no ser que forcemos, devolvera true sin actualizar si el tokem es aun valido
unless force
if valid_token?
return true
end
end
response=@http.get(token_url).to_s
auth=/AUTHTOKEN=(?<token>\w{32})/.match(response)
result=/RESULT=(?<result>\w*)/.match(response)
if true?(result[:result])
@life_token = auth[:token]
else
return false
end
end
def auth_params(grant_type=@grant_type)
#osparams necesarios para una autenticaciton
if grant_type == "refresh_token"
#Ojo, el @refresh_token es efimero ya que solo se tiene en cuenta
# en el momento que se ejecuta get_refresh_token dentro de refresh_token
auth_params = {
refresh_token: @refresh_token,
client_id: @client_id,
client_secret: @client_secret,
grant_type: grant_type
}
else
auth_params = {
access_token: token,
client_id: @client_id,
client_secret: @client_secret,
grant_type: grant_type
}
end
end
def valid_token?
#Alguna consulta que nos verifique el que token es valido.
#Dependiendo del tipo de token tenemos distintas opciones
case check_mode
when "oauth"
valid_oauth_token?
when "apikey"
return @zapikey =! nil
when "auth-token"
return @life_token =! nil
else
raise "Error checking validity of token due an unexpected mode"
end
end
def valid_oauth_token?
@logger.info("Checking if the token is still valid...")
show_current_options
#options = nil
#if token.nil?
# puts "No hay token"
# begin
# options=options_from_json_file({:json_file=>'token.json',:auth_type=>'oauth'})
# rescue Exception => e
# @logger.info("No es posible tratar el fichero de token")
# return false
# end
# @logger.info("VAmos a inilizalizar las opciones")
# pp options
# initialize_options(options) unless options.nil?
#Aqui tenemos que ver si aun es valido
expires = @expires_in
expires = @expiry_time if expires.nil?
unless expires.nil?
expires = DateTime.strptime(expires.to_s,'%Q') if expires.kind_of?String
@logger.info("Comprobando si el token ha expirado")
puts DateTime.now < expires
puts "............................"
return DateTime.now < expires
else
@logger.info("Valid oauth token aun no esta implementado")
return true
end
return false
end
def authorization_request
raise "Only available for OAuth" unless check_mode == "oauth"
#"https://accounts.zoho.com/oauth/v2/auth?scope=ZohoCRM.users.ALL&client_id={client_id}&response_type=code&access_type={"offline"or"online"}&redirect_uri={redirect_uri}"
end
def get(url)
HTTP.use(logging: {logger: @logger}).headers("Authorization" => "Zoho-oauthtoken #{token}").accept(:json).get(url)
end
def get_params(url,payload)
if check_mode == "oauth"
@logger.info("Haciendo el get params de OAuth")
HTTP.use(logging: {logger: @logger}).headers("Authorization" => "Zoho-oauthtoken #{token}").accept(:json).get(url, :params => payload)
else
@logger.info("Haciendo el get params")
puts "........................."
pp @http
puts "........................."
@http.get(url, :params => payload).to_s
end
end
def true?(obj)
obj.to_s.downcase == "true"
end
def post_payload(callback_url, payload)
puts "Posting with payload"
#pp payload
@http.post(callback_url, json: payload)
rescue HTTP::Error
# handle exception
end
def post(url)
puts "Posting"
#pp payload
@http.post(url)
rescue HTTP::Error
# handle exception
end
def normalize_output(output)
raise "#{__method__} Invalid string. I can't normalize this crap!" unless valid_json?(output)
@logger.info "Normalizing..."
return Zoholib::Zoho::Response.new(output)
end
def sanitize(string)
util=ERB
return JSON.parse(util.json_escape)
end
# INIT CONTEXT
def initialize_options(options)
# Utilizamos options para configurar las distintas maneras de autenticarse
# Si las options tienen un json file extraeremos todas las opciones de ahi
options=options_from_json_file(options) if options.has_key?(:json_file)
@logger.info("Opciones en blanco") if options.empty?
raise "Invalid auth type" unless validate_auth_type(options)
raise "Invalid options" unless validate_options(options)
case check_mode
when "apikey"
@user=nil
@pass=nil
@scope=nil
@client_id=nil
@client_secret=nil
@code=nil
@grant_type=nil
@access_token=nil
@refresh_token=nil
@api_domain=nil
@toke_type=nil
@expires_in=nil
@zapikey=options[:zapikey]
@redirect_uri=nil
when "oauth"
@user=nil
@pass=nil
@scope=options[:scope]
@client_id=options[:client_id]
@client_secret=options[:client_secret]
@code=options[:code]
@grant_type=options[:grant_type]
@access_token=options[:access_token]
@refresh_token=options[:refresh_token]
@api_domain=nil
@toke_type=nil
@expires_in=options[:expiry_time]
@zapikey=nil
@redirect_uri=options[:redirect_uri]
when "auth-token"
@user=options[:user]
@pass=options[:pass]
@scope=options[:scope]
@client_id=nil
@client_secret=nil
@code=nil
@grant_type=nil
@access_token=nil
@refresh_token=nil
@api_domain=nil
@toke_type=nil
@expires_in=nil
@zapikey=nil
@redirect_uri=nil
else
raise "Unable to inilizalie!"
end
@scope=@scope.join(",") if @scope.kind_of?Array
return true
end
def options_from_json_file(options)
@logger.info("Parsing options from file...")
raise "Unable to find option json_file!" unless options.has_key?(:json_file)
begin
content = File.read(options[:json_file])
new_options=JSON.parse(content, :symbolize_names => true)
options.delete(:json_file)
new_options[:expires_in] = DateTime.strptime(new_options[:expiry_time].to_s,'%Q')
options = options.merge(new_options)
rescue Exception => e
puts e.backtrace
raise "Unable to read the json_file: #{e.message}"
end
#pp options
return options
end
def load_previous_token(previous_token="token.json")
@logger.info("Loading_previous_token...")
begin
content = File.read(previous_token)
new_options=JSON.parse(content, :symbolize_names => true)
@logger.info("LOADED FROM FILE:#{new_options} ")
#new_options[:expires_in] = DateTime.strptime(new_options[:expiry_time].to_s,'%Q')
initialize_options(new_options)
rescue Exception => e
puts e.backtrace
raise "Unable to load previous token: #{e.message}"
end
#pp options
return true
end
def check_mode
#Check if im running in oauthv2 or auth-token
return @auth_type
end
def validate_auth_type(options)
#Comprobamos que forma parte de uno de los metodos de autenticacion soporteados
unless ["oauth","apikey","auth-token"].member?(options[:auth_type])
@logger.warn("Initializing with detault auth_type oauth")
@auth_type="oauth"
else
@auth_type = options[:auth_type]
end
return true
end
def required_options(auth_type=@auth_type)
#Dependiendo del tipo de autenticacion, indica cuales son las optciones requeridas
case auth_type
when "oauth"
return [:client_id,:client_secret,:scope]
#return [:client_id,:client_secret,:code,:grant_type,:scope]
when "apikey"
return [:zapikey]
when "auth-token"
return [:user,:pass,:scope]
else
raise "#{__method__} Unknown auth_type #{auth_type}"
end
end
def validate_options(options)
# Comprueba que segun el metodo de autenticacion dado, nos han pasado todas la opciones necesarias
required_options.each do |key|
raise "#{__method__} Option #{key.to_s} not found!" unless options.has_key?(key)
raise "#{__method__} Option #{key.to_s} not found!" if options[key].nil?
end
return true
end
def validate_scope
# existen distintos tipos de scopes dependiendo del metodo
# auth-token
# scope="ZohoCreator/creatorapi"
# oauth
# scope="ZohoCRM.modules.ALL,ZohoCRM.settings.ALL"
# apikey
# scope=nil
return true
end
def persist_token
expiry_time = @expires_in.strftime("%Q") if @expires_in.kind_of?DateTime
token_information = {
:client_id =>@client_id,
:client_secret => @client_secret,
:scope => @scope,
:access_token => token,
:code => @code,
:expiry_time => expiry_time,
:grant_type => @grant_type,
:redirect_uri => @redirect_uri,
:refresh_token => @refresh_token,
:auth_type => 'oauth'
}
f=File.new("token.json","w+")
f.print(token_information.to_json)
f.close
end
def show_current_options
@logger.info("auth_type : #{@auth_type}")
@logger.info("user : #{@user}")
@logger.info("pass : #{@pass}")
@logger.info("scope : #{@scope}")
@logger.info("life_token : #{@life_token}")
@logger.info("client_id : #{@client_id}")
@logger.info("client_secret : #{@client_secret}")
@logger.info("code : #{@code}")
@logger.info("grant_type : #{@grant_type}")
@logger.info("access_token : #{@access_token}")
@logger.info("refresh_token : #{@refresh_token}")
@logger.info("api_domain : #{@api_domain}")
@logger.info("token_type : #{@token_type}")
@logger.info("expires_in : #{@expires_in}")
@logger.info("expiry_time : #{@expiry_time}")
@logger.info("zapikey : #{@zapikey}")
@logger.info("redirect_uri : #{@redirect_uri}")
end
end
end
require 'httparty'
require 'json'
module Zoholib
class ZohoRequest
include HTTParty
#debug_output $stdout
base_uri "https://accounts.zoho.com/oauth/v2/"
#https://accounts.zoho.com/oauth/v2/auth?
#https://accounts.zoho.com/oauth/v2/token?
headers 'Content-Type' => 'application/json'
attr_reader :subdomain, :uri
end
end
\ No newline at end of file
# frozen_string_literal: true
require 'bundler/setup'
require 'zoholib'
RSpec.configure do |conf|
conf.before(:all) do
Zoholib.configure do |config|
config.client_id = "client_id"
config.client_secret = "client_secret"
config.scope = "scope"
config.refresh_token = "refresh_token"
end
end
conf.add_formatter(:documentation)
# Enable flags like --only-failures and --next-failure
conf.example_status_persistence_file_path = '.rspec_status'
# Disable RSpec exposing methods globally on `Module` and `main`
conf.disable_monkey_patching!
conf.expect_with :rspec do |c|
c.syntax = :expect
end
end
require './lib/zoholib'
RSpec.describe Zoholib do
it 'has a version number' do
expect(Zoholib::VERSION).not_to be nil
end
it 'does something useful' do
expect(true).to eq(true)
end
describe 'Configuration' do
it 'The configuration shuld be a Zoholib::Configuration' do
expect(Zoholib.configuration).to be_a_kind_of(Zoholib::Configuration)
end
it 'The client_id is a string' do
expect(Zoholib.configuration.client_id).to be_an(String)
end
it 'The client_secret is a string' do
expect(Zoholib.configuration.client_secret).to be_an(String)
end
it 'The scopes is a string' do
expect(Zoholib.configuration.scopes).to be_an(String)
end
it 'The refresh_token is a string' do
expect(Zoholib.configuration.refresh_token).to be_an(String)
end
end
end
\ No newline at end of file
# frozen_string_literal: true
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'zoholib/version'
Gem::Specification.new do |spec|
spec.name = 'zoholib'
spec.version = Zoholib::VERSION
spec.date = '2020-04-25'
spec.authors = ['Wedoops.io']
spec.email = 'wedoops@wedoops.io'
spec.summary = 'Zoho Library'
spec.description = 'A library for interact with ZOHO REST API'
spec.homepage = 'http://www.wedoops.io'
spec.license = 'MIT'
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
if spec.respond_to?(:metadata)
spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
else
raise 'RubyGems 2.0 or newer is required to protect against ' \
'public gem pushes.'
end
spec.files = `git ls-files -z`.split("\x0").reject do |f|
f.match(%r{^(test|spec|features)/})
end
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']
spec.add_development_dependency 'bundler', '~> 1.15'
spec.add_development_dependency 'rake', '~> 13.0.1'
spec.add_development_dependency 'rspec', '~> 3.0'
#spec.add_development_dependency 'httparty','~> 0.18.0'
#spec.add_development_dependency 'http','~> 4.4.0'
#spec.add_development_dependency 'logger','~> 1.4.2'
#spec.add_development_dependency 'savon','~> 2.12.0'
#spec.add_development_dependency 'sib-api-v3-sdk','~> 5.3.0'
#spec.add_development_dependency 'launchy','~> 2.5.0'
#
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment