Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
intelligent_foods (0.0.0)
batchline-solutions-core (0.0.0)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -76,8 +76,8 @@ PLATFORMS
arm64-darwin-21

DEPENDENCIES
batchline_solutions!
factory_bot
intelligent_foods!
json-schema
pry (~> 0.14)
rake (~> 13.0)
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# IntelligentFoods
# BatchlineSolutions

## Installation

Expand All @@ -18,7 +18,7 @@ executing:
```
require "intelligent-foods-ruby"

IntelligentFoods.configure do |config|
BatchlineSolutions.configure do |config|
config.client_id = "XXXXXX"
config.client_secret = "YYYYYY"
config.environment = "preview"
Expand All @@ -28,7 +28,7 @@ end
### Authentication

```
@client = IntelligentFoods.client
@client = BatchlineSolutions.client
@client.authenticate!
```

Expand All @@ -52,7 +52,7 @@ For `rspec`, add the following line to your `spec/rails_helper.rb` or
`spec/spec_helper` if `rails_helper` does not exist:

```
require "intelligent_foods/rspec"
require "core/rspec"
```

## Contributing
Expand All @@ -68,5 +68,5 @@ The gem is available as open source under the terms of the [MIT License](https:/

## Code of Conduct

Everyone interacting in the IntelligentFoods project's codebases, issue
Everyone interacting in the BatchlineSolutions project's codebases, issue
trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/intelligent-foods-ruby/blob/main/CODE_OF_CONDUCT.md).
2 changes: 1 addition & 1 deletion bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# frozen_string_literal: true

require "bundler/setup"
require "intelligent_foods"
require "core"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this is BLS::Core should the insertion point be renamed too?


# 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.
Expand Down
21 changes: 11 additions & 10 deletions intelligent-foods-ruby.gemspec
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
# frozen_string_literal: true

require_relative "lib/intelligent_foods/version"
require_relative "lib/core/version"

Gem::Specification.new do |spec|
spec.name = "intelligent_foods"
spec.version = IntelligentFoods::VERSION
spec.authors = ["Chris Woodford"]
spec.email = ["chris@gobble.com"]

spec.summary = "Ruby SDK for the Intelligent Foods API"
spec.homepage = "https://intelligentfoods.com"
spec.name = "batchline-solutions-core"
spec.version = Bls::Core::VERSION
spec.authors = ["Chris Woodford", "Rohan Likhite"]
spec.email = ["chris.woodford@batchline.solutions",
"rohan.likhite@batchline.solutions"]

spec.summary = "Ruby SDK for the Batchline Solutions Core API"
spec.homepage = "https://batchline.solutions.com"
spec.license = "MIT"
spec.required_ruby_version = ">= 3.1"

spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"

spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "https://github.com/Intelligent-Foods/intelligent-foods-ruby"
spec.metadata["changelog_uri"] = "https://github.com/Intelligent-Foods/intelligent-foods-rub/CHANGELOG.mdy"
spec.metadata["source_code_uri"] = "https://github.com/batchlinesolutions/core-ruby"
spec.metadata["changelog_uri"] = "https://github.com/batchlinesolutions/core-ruby//CHANGELOG.mdy"

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
Expand Down
42 changes: 42 additions & 0 deletions lib/core.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require "base64"
require "json"
require "net/http"
require "ostruct"

require "core/api_client"
require "core/api/v1"
require "core/api/v1/authenticator"
require "core/api_operations/retrieve"
require "core/authorization/base"
require "core/authorization/basic"
require "core/authorization/blank"
require "core/resources/api_error"
require "core/authorization/bearer"
require "core/resources/object"
require "core/resources/inventory_level"
require "core/resources/order"
require "core/resources/order_item"
require "core/serializers/order_item_serializer"
require "core/resources/menu"
require "core/resources/menu_item"
require "core/serializers/menu_item_serializer"
require "core/resources/recipient"
require "core/serializers/recipient_serializer"
require "core/version"
require "core/errors"

module Bls
module Core
class Error < StandardError; end

class << self
attr_accessor :client_id, :client_secret, :environment

def configure
yield self
end
end
end
end
70 changes: 70 additions & 0 deletions lib/core/api/v1.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

module Bls
module Core
class V1
attr_accessor :authentication
attr_reader :environment, :username, :password

PRODUCTION = "production"
STAGING = "staging"

def initialize(username: nil, password: nil, authentication: nil,
environment: STAGING)
@authentication = authentication
@environment = environment
@username = username
@password = password
end

def base_url
"https://api.sunbasket.#{tld}/partner/v1"
end

def authenticate!
return if authenticated?

authenticator = V1::Authenticator.build(self)
@authentication = authenticator.save!
end

def client
build_api_client!
end

def authenticated?
authentication.present?
end

def reset_authentication
@authentication = nil
self
end

def self.build(config: Bls::Core)
new(environment: config.environment,
username: config.client_id,
password: config.client_secret)
end

protected

def build_api_client!
authenticate!
Bls::Core::ApiClient.build(self)
end

def tld
@tld ||= determine_tld
end

def determine_tld
if environment == PRODUCTION
"com"
else
"dev"
end
end
end
end
end
54 changes: 54 additions & 0 deletions lib/core/api/v1/authenticator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

module Bls
module Core
class V1::Authenticator
attr_accessor :base_url, :client_id, :client_secret

def initialize(base_url:, client_id:, client_secret:)
@base_url = base_url
@client_id = client_id
@client_secret = client_secret
end

def save!
path = "#{base_url}/token"
body = { client_id: client_id, client_secret: client_secret }
auth = Authorization::Basic.
factory(client_id: client_id, client_secret: client_secret)
client = Core::ApiClient.new(authentication: auth)
response = client.post(path: path, body: body)
handle_authentication_response!(response: response.data)
end

def self.build(api)
new(base_url: api.base_url,
client_id: api.username,
client_secret: api.password)
end

protected

def handle_authentication_response!(response:)
if response_has_errors?(response)
handle_errors!
else
handle_successful_authentication_response(response)
end
end

def response_has_errors?(response)
response.has_key?(:error)
end

def handle_successful_authentication_response(response)
access_token = response[:access_token]
Core::Authorization::Bearer.new(token: access_token)
end

def handle_errors!
raise AuthenticationError.new(status: 401, title: "Authentication Failed")
end
end
end
end
91 changes: 91 additions & 0 deletions lib/core/api_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# frozen_string_literal: true

module Bls
module Core
class ApiClient
attr_reader :api, :authentication

def initialize(api: nil, authentication: nil)
@api = api
@authentication = authentication || Authorization::Blank.new
end

def post(path:, body:)
uri = URI(path)
request = build_request_with_body(uri: uri, body: body)
execute_request(request: request, uri: request.uri)
end

def delete(path:, **)
uri = URI(path)
request = Net::HTTP::Delete.new(uri)
execute_request(request: request, uri: request.uri)
end

def get(path:, **)
uri = URI(path)
request = Net::HTTP::Get.new(uri)
execute_request(request: request, uri: request.uri)
end

def execute_request(request:, uri:)
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
request["Authorization"] = authentication.header
response = http.request(request)
handle_response(response: response)
end
end

def self.build(api)
new(api: api, authentication: api.authentication)
end

protected

def build_request_with_body(uri:, body:)
request = Net::HTTP::Post.new(uri)
request.body = body.to_json
request["content-type"] = "application/json"
request
end

def handle_response(response:)
if authentication_failed?(response.code)
handle_authentication_error!
else
body = parse_response_body(response)
OpenStruct.new(data: body, success?: request_successful?(response.code))
end
end

def handle_authentication_error!
api.reset_authentication
raise AuthenticationError.new(status: 401,
title: "Authentication Failed")
end

def parse_response_body(response)
return {} if empty_response?(response.code)
return {} if redirection?(response.code)

JSON.parse(response.body, symbolize_names: true)
end

def empty_response?(response_code)
response_code.to_i == 204
end

def redirection?(response_code)
response_code.to_i.between?(300, 399)
end

def request_successful?(response_code)
response_code.to_i.between?(200, 299)
end

def authentication_failed?(response_code)
response_code.to_i == 401
end
end
end
end
31 changes: 31 additions & 0 deletions lib/core/api_operations/retrieve.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module Bls
module Core
module ApiOperations
module Retrieve
def retrieve(id)
resource = new(id: id)
api_client = resource.client
response = api_client.get(path: resource.resource_path)
if response.success?
build_from_response(response.data)
else
raise ResourceRetrievalError.build(response)
end
end

def retrieve_all
resource = new
api_client = resource.client
response = api_client.get(path: resource.resources_path)
if response.success?
response.data.map { |id| new(id: id) }
else
raise ResourceRetrievalError.build(response)
end
end
end
end
end
end
Loading