sinatra-embedded-shopify-app 0.5.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,126 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ sinatra-embedded-shopify-app (0.5.16)
5
+ activesupport
6
+ omniauth (~> 2.0, >= 2.0.4)
7
+ omniauth-shopify-oauth2 (~> 2.3, >= 2.3.2)
8
+ shopify_api (= 9.5.1)
9
+ sinatra (>= 2.2, < 3.1)
10
+ sinatra-activerecord (~> 2.0.9)
11
+
12
+ GEM
13
+ remote: https://rubygems.org/
14
+ specs:
15
+ activemodel (7.0.4.3)
16
+ activesupport (= 7.0.4.3)
17
+ activemodel-serializers-xml (1.0.2)
18
+ activemodel (> 5.x)
19
+ activesupport (> 5.x)
20
+ builder (~> 3.1)
21
+ activerecord (7.0.4.3)
22
+ activemodel (= 7.0.4.3)
23
+ activesupport (= 7.0.4.3)
24
+ activeresource (6.0.0)
25
+ activemodel (>= 6.0)
26
+ activemodel-serializers-xml (~> 1.0)
27
+ activesupport (>= 6.0)
28
+ activesupport (7.0.4.3)
29
+ concurrent-ruby (~> 1.0, >= 1.0.2)
30
+ i18n (>= 1.6, < 2)
31
+ minitest (>= 5.1)
32
+ tzinfo (~> 2.0)
33
+ builder (3.2.4)
34
+ byebug (11.1.3)
35
+ concurrent-ruby (1.2.2)
36
+ fakeweb (1.3.0)
37
+ faraday (2.7.4)
38
+ faraday-net_http (>= 2.0, < 3.1)
39
+ ruby2_keywords (>= 0.0.4)
40
+ faraday-net_http (3.0.2)
41
+ graphql (2.0.21)
42
+ graphql-client (0.18.0)
43
+ activesupport (>= 3.0)
44
+ graphql
45
+ hashie (5.0.0)
46
+ i18n (1.12.0)
47
+ concurrent-ruby (~> 1.0)
48
+ jwt (2.7.0)
49
+ mini_portile2 (2.8.2)
50
+ minitest (5.18.0)
51
+ mocha (1.15.0)
52
+ multi_xml (0.6.0)
53
+ mustermann (2.0.2)
54
+ ruby2_keywords (~> 0.0.1)
55
+ oauth2 (2.0.9)
56
+ faraday (>= 0.17.3, < 3.0)
57
+ jwt (>= 1.0, < 3.0)
58
+ multi_xml (~> 0.5)
59
+ rack (>= 1.2, < 4)
60
+ snaky_hash (~> 2.0)
61
+ version_gem (~> 1.1)
62
+ omniauth (2.1.1)
63
+ hashie (>= 3.4.6)
64
+ rack (>= 2.2.3)
65
+ rack-protection
66
+ omniauth-oauth2 (1.8.0)
67
+ oauth2 (>= 1.4, < 3)
68
+ omniauth (~> 2.0)
69
+ omniauth-shopify-oauth2 (2.3.2)
70
+ activesupport
71
+ omniauth-oauth2 (~> 1.5)
72
+ pg (1.4.3)
73
+ rack (2.2.6.4)
74
+ rack-protection (2.2.4)
75
+ rack
76
+ rack-test (2.0.2)
77
+ rack (>= 1.3)
78
+ rake (13.0.6)
79
+ ruby2_keywords (0.0.5)
80
+ shopify_api (9.5.1)
81
+ activeresource (>= 4.1.0)
82
+ graphql-client
83
+ rack
84
+ sinatra (2.2.4)
85
+ mustermann (~> 2.0)
86
+ rack (~> 2.2)
87
+ rack-protection (= 2.2.4)
88
+ tilt (~> 2.0)
89
+ sinatra-activerecord (2.0.26)
90
+ activerecord (>= 4.1)
91
+ sinatra (>= 1.0)
92
+ sinatra-flash (0.3.0)
93
+ sinatra (>= 1.0.0)
94
+ snaky_hash (2.0.1)
95
+ hashie
96
+ version_gem (~> 1.1, >= 1.1.1)
97
+ sqlite3 (1.5.1)
98
+ mini_portile2 (~> 2.8.0)
99
+ sqlite3 (1.5.1-x86_64-linux)
100
+ tilt (2.0.11)
101
+ tzinfo (2.0.6)
102
+ concurrent-ruby (~> 1.0)
103
+ version_gem (1.1.2)
104
+
105
+ PLATFORMS
106
+ ruby
107
+ x86_64-linux
108
+
109
+ DEPENDENCIES
110
+ byebug
111
+ fakeweb
112
+ minitest
113
+ mocha
114
+ pg
115
+ rack-test
116
+ rake (>= 12.3.3)
117
+ sinatra-activerecord
118
+ sinatra-embedded-shopify-app!
119
+ sinatra-flash
120
+ sqlite3
121
+
122
+ RUBY VERSION
123
+ ruby 2.7.7p221
124
+
125
+ BUNDLED WITH
126
+ 2.4.12
data/example/Procfile ADDED
@@ -0,0 +1 @@
1
+ web: bundle exec rackup config.ru -p $PORT
data/example/README.md ADDED
@@ -0,0 +1,4 @@
1
+ New Sinatra Embedded Shopify App
2
+ =======================
3
+
4
+ Fill me in!
data/example/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'sinatra/activerecord/rake'
2
+ require 'rake/testtask'
3
+ require './src/app'
4
+
5
+ namespace :test do
6
+ task :prepare do
7
+ `RACK_ENV=test rake db:create`
8
+ `RACK_ENV=test rake db:migrate`
9
+ `RACK_ENV=test SECRET=secret rake db:seed`
10
+ end
11
+ end
12
+
13
+ task :test do
14
+ Rake::TestTask.new do |t|
15
+ t.pattern = 'test/*_test.rb'
16
+ t.libs << 'test'
17
+ t.verbose = true
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ development:
2
+ adapter: sqlite3
3
+ database: db/development.sqlite3
4
+ pool: 5
5
+
6
+ production:
7
+ url: <%= ENV['DATABASE_URL'] %>
8
+
9
+ test:
10
+ adapter: sqlite3
11
+ database: "db/test.sqlite3"
12
+ pool: 8
data/example/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require './src/app'
2
+ SinatraApp.set :bind, '0.0.0.0'
3
+ SinatraApp.run!
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateShops < ActiveRecord::Migration[5.1]
4
+ def self.up
5
+ create_table :shops do |t|
6
+ t.string :shopify_domain
7
+ t.string :shopify_token
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ drop_table :shops
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddIndexToShops < ActiveRecord::Migration[5.1]
4
+ def self.up
5
+ add_index :shops, :shopify_domain
6
+ end
7
+
8
+ def self.down
9
+ remove_index :shops, :shopify_domain
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema[7.0].define(version: 2014_04_14_042317) do
14
+ create_table "shops", force: :cascade do |t|
15
+ t.string "shopify_domain"
16
+ t.string "shopify_token"
17
+ t.index ["shopify_domain"], name: "index_shops_on_shopify_domain"
18
+ end
19
+
20
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ shop = Shop.create(shopify_domain: 'testshop.myshopify.com', shopify_token: 'token')
Binary file
Binary file
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra/sinatra-embedded-shopify-app'
4
+ require 'sinatra/flash'
5
+
6
+ # SinatraApp
7
+ class SinatraApp < Sinatra::Base
8
+ register Sinatra::Shopify
9
+ register Sinatra::Flash
10
+
11
+ # set the scope that your app needs, read more here:
12
+ # http://docs.shopify.com/api/tutorials/oauth
13
+ set :scope, 'read_products, read_orders'
14
+
15
+ # Your App's Home page
16
+ # this is a simple example that fetches some products
17
+ # from Shopify and displays them inside your app
18
+ get '/' do
19
+ shopify_session do |shop_name|
20
+ @shop = ShopifyAPI::Shop.current
21
+ @products = ShopifyAPI::Product.find(:all, params: { limit: 10 })
22
+ erb :home
23
+ end
24
+ end
25
+
26
+ # this endpoint recieves the uninstall webhook
27
+ # and cleans up data, add to this endpoint as your app
28
+ # stores more data.
29
+ shopify_webhook '/uninstall' do |shop_name, params|
30
+ Shop.find_by(shopify_domain: shop_name).destroy
31
+ end
32
+
33
+ private
34
+
35
+ # This method gets called when your app is installed.
36
+ # setup any webhooks or services you need on Shopify
37
+ # inside here.
38
+ def after_shopify_auth
39
+ # shopify_session do
40
+ # create an uninstall webhook, this webhook gets sent
41
+ # when your app is uninstalled from a shop. It is good
42
+ # practice to clean up any data from a shop when they
43
+ # uninstall your app:
44
+
45
+ # uninstall_webhook = ShopifyAPI::Webhook.new(
46
+ # topic: 'app/uninstalled',
47
+ # address: "#{base_url}/uninstall",
48
+ # format: 'json'
49
+ # )
50
+ # begin
51
+ # uninstall_webhook.save!
52
+ # rescue => e
53
+ # raise unless uninstall_webhook.persisted?
54
+ # end
55
+ # end
56
+ end
57
+ end
@@ -0,0 +1,81 @@
1
+ require 'test_helper'
2
+ require './src/app'
3
+
4
+ class MockShop
5
+ def initialize(shop_name)
6
+ @shop_name = shop_name
7
+ end
8
+
9
+ def myshopify_domain
10
+ @shop_name
11
+ end
12
+ end
13
+
14
+ class AppTest < Minitest::Test
15
+ def app
16
+ SinatraApp
17
+ end
18
+
19
+ def setup
20
+ @shop_name = 'testshop.myshopify.com'
21
+ @shopify_shop = MockShop.new(@shop_name)
22
+ end
23
+
24
+ def test_root_with_session
25
+ set_session
26
+ api_url = "https://testshop.myshopify.com/admin/api/#{app.settings.api_version}"
27
+ fake "#{api_url}/shop.json", body: {myshopify_domain: @shop_name}.to_json
28
+ fake "#{api_url}/products.json?limit=10", body: '{}'
29
+ get '/'
30
+ assert last_response.ok?
31
+ end
32
+
33
+ def test_root_with_session_activates_api
34
+ set_session
35
+ SinatraApp.any_instance.expects(:activate_shopify_api).with(@shop_name, 'token')
36
+ ShopifyAPI::Shop.expects(:current).returns(@shopify_shop)
37
+ ShopifyAPI::Product.expects(:find).returns([])
38
+ get '/'
39
+ assert last_response.ok?
40
+ end
41
+
42
+ def test_root_without_session_redirects_to_install
43
+ get '/'
44
+ assert_equal 302, last_response.status
45
+ assert_equal 'http://example.org/login', last_response.location
46
+ end
47
+
48
+ def test_root_with_shop_redirects_to_auth
49
+ get '/?shop=othertestshop.myshopify.com'
50
+ assert_equal 'http://example.org/login?shop=othertestshop.myshopify.com', last_response.location
51
+ end
52
+
53
+ def test_root_with_session_and_new_shop_redirects_to_auth
54
+ set_session
55
+ get '/?shop=othertestshop.myshopify.com'
56
+ assert_equal 'http://example.org/login?shop=othertestshop.myshopify.com', last_response.location
57
+ end
58
+
59
+ def test_root_rescues_UnauthorizedAccess_clears_session_and_redirects
60
+ set_session
61
+ SinatraApp.any_instance.expects(:activate_shopify_api).with(@shop_name, 'token')
62
+ SinatraApp.any_instance.expects(:clear_session)
63
+ ShopifyAPI::Shop.expects(:current).raises(ActiveResource::UnauthorizedAccess.new('UnauthorizedAccess'))
64
+ get '/'
65
+ assert_equal 302, last_response.status
66
+ assert_equal 'http://example.org/', last_response.location
67
+ end
68
+
69
+ def test_uninstall_webhook_endpoint
70
+ SinatraApp.any_instance.expects(:verify_shopify_webhook).returns(true)
71
+ Shop.any_instance.expects(:destroy)
72
+ post '/uninstall', '{}', 'HTTP_X_SHOPIFY_SHOP_DOMAIN' => @shop_name
73
+ assert last_response.ok?
74
+ end
75
+
76
+ private
77
+
78
+ def set_session(shop = 'testshop.myshopify.com', token = 'token')
79
+ SinatraApp.any_instance.stubs(:session).returns(shopify: { shop: shop, token: token })
80
+ end
81
+ end
@@ -0,0 +1,31 @@
1
+ $VERBOSE = nil
2
+
3
+ ENV['RACK_ENV'] = 'test'
4
+ ENV['SECRET'] = 'secret'
5
+
6
+ require 'minitest/autorun'
7
+ require 'rack/test'
8
+ require 'mocha/minitest'
9
+ require 'fakeweb'
10
+
11
+ FakeWeb.allow_net_connect = false
12
+
13
+ module Helpers
14
+ include Rack::Test::Methods
15
+
16
+ def load_fixture(name)
17
+ File.read("./test/fixtures/#{name}")
18
+ end
19
+
20
+ def fake(url, options = {})
21
+ method = options.delete(:method) || :get
22
+ body = options.delete(:body) || '{}'
23
+ format = options.delete(:format) || :json
24
+
25
+ FakeWeb.register_uri(method, url, { body: body, status: 200, content_type: "application/#{format}" }.merge(options))
26
+ end
27
+ end
28
+
29
+ class Minitest::Test
30
+ include Helpers
31
+ end
@@ -0,0 +1,25 @@
1
+ <script type="text/javascript">
2
+ var AppBridge = window['app-bridge'];
3
+
4
+ var actions = AppBridge.actions;
5
+ var Toast = actions.Toast;
6
+
7
+ <% if flash[:notice] %>
8
+ var notice = Toast.create(app, {
9
+ message: "<%= flash[:notice] %>",
10
+ duration: 5000
11
+ });
12
+
13
+ notice.dispatch(Toast.Action.SHOW);
14
+ <% end %>
15
+
16
+ <% if flash[:error] %>
17
+ var notice = Toast.create(app, {
18
+ message: "<%= flash[:error] %>",
19
+ duration: 5000,
20
+ isError: true,
21
+ });
22
+
23
+ notice.dispatch(Toast.Action.SHOW);
24
+ <% end %>
25
+ </script>
@@ -0,0 +1,10 @@
1
+ <script type="text/javascript">
2
+ var AppBridge = window['app-bridge'];
3
+
4
+ var actions = AppBridge.actions;
5
+ var TitleBar = actions.TitleBar;
6
+
7
+ var titleBar = TitleBar.create(app, {
8
+ icon: '<%= "#{base_url}/icon.png" %>'
9
+ });
10
+ </script>
@@ -0,0 +1,11 @@
1
+ <h3>Shopify Sinatra App</h3>
2
+ <p>Welcome!</p>
3
+
4
+ <h3>Products</h3>
5
+ <ul>
6
+ <% @products.each do |product| %>
7
+ <li><a href=<%="https://#{@shop.myshopify_domain}/admin/products/#{product.id}"%> target="_blank"> <%= product.id %> </a></li>
8
+ <% end %>
9
+ </ul>
10
+
11
+ <a href="/logout">Logout</a>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <script src="https://unpkg.com/@shopify/app-bridge@3"></script>
5
+ <script type="text/javascript">
6
+ var AppBridge = window['app-bridge'];
7
+
8
+ var host = '<%= shop_host %>';
9
+ var apiKey = '<%= SinatraApp.settings.api_key %>';
10
+
11
+ var app = AppBridge.createApp({
12
+ apiKey: apiKey,
13
+ host: host
14
+ });
15
+ </script>
16
+ <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha256-7s5uDGW3AHqw6xtJmNNtr+OBRJUlgkNJEo78P4b0yRw= sha512-nNo+yCHEyn0smMxSswnf/OnX6/KwJuZTlNZBjauKhTK0c+zT+q5JOCx0UFhXQ6rJR9jg6Es8gPuD2uZcYDLqSw==" crossorigin="anonymous">
17
+ </head>
18
+
19
+ <body>
20
+ <div class="container">
21
+ <%= erb :'_top_bar', layout: false, locals: locals %>
22
+ <%= erb :'_flash_messages', layout: false, locals: locals %>
23
+ <%= yield %>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,42 @@
1
+ <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha256-7s5uDGW3AHqw6xtJmNNtr+OBRJUlgkNJEo78P4b0yRw= sha512-nNo+yCHEyn0smMxSswnf/OnX6/KwJuZTlNZBjauKhTK0c+zT+q5JOCx0UFhXQ6rJR9jg6Es8gPuD2uZcYDLqSw==" crossorigin="anonymous">
2
+
3
+ <style>
4
+ body {
5
+ background: #fff url(<%="#{base_url}/legend.gif"%>) no-repeat fixed right 130px;
6
+ }
7
+
8
+ .form-large input {
9
+ font-size: 18px;
10
+ padding: 8px;
11
+ height: auto;
12
+ line-height: normal;
13
+ }
14
+
15
+ .form-wide {
16
+ width: 480px;
17
+ }
18
+ </style>
19
+
20
+ <body>
21
+ <div style="margin-top: 100px"></div>
22
+
23
+ <div class="container">
24
+ <h1>Shopify Sinatra App</h1>
25
+ <h2><small>This app requires you to login to start using it.</small></h2>
26
+ </div>
27
+
28
+ <div style="margin-top: 30px"></div>
29
+
30
+ <div class="container">
31
+ <form id="login" role="form" class="form-large" action="/auth/shopify" method="post">
32
+ <input type="hidden" name="authenticity_token" value="<%= env['rack.session'][:csrf] %>" />
33
+ <div class="input-group form-wide">
34
+ <input class="form-control" name="shop" value="<%= params['shop'] %>" placeholder="your-shop-url.myshopify.com">
35
+ <span class="input-group-btn">
36
+ <input class="btn" type="submit" value="Login" />
37
+ </span>
38
+ </div>
39
+ </form>
40
+ </div>
41
+ </body>
42
+