shopify-sinatra-app 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +137 -0
- data/LICENSE +20 -0
- data/README.md +148 -0
- data/bin/shopify-sinatra-app-generator +48 -0
- data/lib/generator/Gemfile +30 -0
- data/lib/generator/Procfile +1 -0
- data/lib/generator/README.md +4 -0
- data/lib/generator/Rakefile +48 -0
- data/lib/generator/config/app.yml +6 -0
- data/lib/generator/config/database.yml +9 -0
- data/lib/generator/config.ru +2 -0
- data/lib/generator/db/.keep +0 -0
- data/lib/generator/db/migrate/20140413221328_create_shops.rb +12 -0
- data/lib/generator/db/migrate/20140414042317_add_index_to_shops.rb +9 -0
- data/lib/generator/lib/app.rb +38 -0
- data/lib/generator/public/icon.png +0 -0
- data/lib/generator/views/_top_bar.erb +17 -0
- data/lib/generator/views/home.erb +17 -0
- data/lib/generator/views/install.erb +9 -0
- data/lib/generator/views/layouts/application.erb +21 -0
- data/lib/sinatra/shopify-sinatra-app.rb +248 -0
- data/shopify-sinatra-app.gemspec +34 -0
- metadata +279 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ecb9d81878d71aa112a589d76932f041c53e1d9a
|
4
|
+
data.tar.gz: 81d6eb212418d81c89fbad68595d3b6fdcde01b9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a9738522aa12eec39f3b87267a0a72af1b01788dcdc0acba8280b83b53fabb28ae71dad9ef9a19329fd62f273ebf4abd014f9f07fbb7db3ee9dc61ba5997bad7
|
7
|
+
data.tar.gz: 176f8fc34e159c4f01ea4eb9f5f779a6bc4ff29a85fab83562c8d78c01bb2448a9848cea1484b3960cb3bad5002208705bc622a518b8b152df82418e8fe5e644
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/Gemfile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
ruby "2.0.0"
|
3
|
+
|
4
|
+
gem 'sinatra'
|
5
|
+
gem 'sinatra-redis'
|
6
|
+
gem 'sinatra-activerecord'
|
7
|
+
gem 'sinatra-twitter-bootstrap', require: 'sinatra/twitter-bootstrap'
|
8
|
+
gem 'rack-flash3', require: 'rack-flash'
|
9
|
+
gem 'activesupport'
|
10
|
+
gem 'attr_encrypted'
|
11
|
+
gem 'foreman'
|
12
|
+
gem 'rake'
|
13
|
+
|
14
|
+
gem 'resque', '~> 1.22.0'
|
15
|
+
|
16
|
+
gem 'omniauth-shopify-oauth2'
|
17
|
+
gem 'shopify_api'
|
18
|
+
|
19
|
+
group :production do
|
20
|
+
gem 'pg'
|
21
|
+
end
|
22
|
+
|
23
|
+
group :development do
|
24
|
+
gem 'sqlite3'
|
25
|
+
gem 'rack-test'
|
26
|
+
gem 'fakeweb'
|
27
|
+
gem 'mocha', require: false
|
28
|
+
gem 'byebug'
|
29
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (4.1.1)
|
5
|
+
activesupport (= 4.1.1)
|
6
|
+
builder (~> 3.1)
|
7
|
+
activerecord (4.1.1)
|
8
|
+
activemodel (= 4.1.1)
|
9
|
+
activesupport (= 4.1.1)
|
10
|
+
arel (~> 5.0.0)
|
11
|
+
activeresource (4.0.0)
|
12
|
+
activemodel (~> 4.0)
|
13
|
+
activesupport (~> 4.0)
|
14
|
+
rails-observers (~> 0.1.1)
|
15
|
+
activesupport (4.1.1)
|
16
|
+
i18n (~> 0.6, >= 0.6.9)
|
17
|
+
json (~> 1.7, >= 1.7.7)
|
18
|
+
minitest (~> 5.1)
|
19
|
+
thread_safe (~> 0.1)
|
20
|
+
tzinfo (~> 1.1)
|
21
|
+
arel (5.0.1.20140414130214)
|
22
|
+
attr_encrypted (1.3.2)
|
23
|
+
encryptor (>= 1.3.0)
|
24
|
+
builder (3.2.2)
|
25
|
+
byebug (3.1.2)
|
26
|
+
columnize (~> 0.8)
|
27
|
+
debugger-linecache (~> 1.2)
|
28
|
+
coderay (1.1.0)
|
29
|
+
columnize (0.8.9)
|
30
|
+
debugger-linecache (1.2.0)
|
31
|
+
dotenv (0.7.0)
|
32
|
+
encryptor (1.3.0)
|
33
|
+
fakeweb (1.3.0)
|
34
|
+
faraday (0.9.0)
|
35
|
+
multipart-post (>= 1.2, < 3)
|
36
|
+
foreman (0.64.0)
|
37
|
+
dotenv (~> 0.7.0)
|
38
|
+
thor (>= 0.13.6)
|
39
|
+
hashie (2.1.1)
|
40
|
+
i18n (0.6.9)
|
41
|
+
json (1.8.1)
|
42
|
+
jwt (1.0.0)
|
43
|
+
metaclass (0.0.4)
|
44
|
+
method_source (0.8.2)
|
45
|
+
minitest (5.3.4)
|
46
|
+
mocha (1.1.0)
|
47
|
+
metaclass (~> 0.0.1)
|
48
|
+
multi_json (1.10.1)
|
49
|
+
multi_xml (0.5.5)
|
50
|
+
multipart-post (2.0.0)
|
51
|
+
oauth2 (0.9.4)
|
52
|
+
faraday (>= 0.8, < 0.10)
|
53
|
+
jwt (~> 1.0)
|
54
|
+
multi_json (~> 1.3)
|
55
|
+
multi_xml (~> 0.5)
|
56
|
+
rack (~> 1.2)
|
57
|
+
omniauth (1.2.1)
|
58
|
+
hashie (>= 1.2, < 3)
|
59
|
+
rack (~> 1.0)
|
60
|
+
omniauth-oauth2 (1.1.2)
|
61
|
+
faraday (>= 0.8, < 0.10)
|
62
|
+
multi_json (~> 1.3)
|
63
|
+
oauth2 (~> 0.9.3)
|
64
|
+
omniauth (~> 1.2)
|
65
|
+
omniauth-shopify-oauth2 (1.1.6)
|
66
|
+
omniauth-oauth2 (~> 1.1.1)
|
67
|
+
pg (0.17.1)
|
68
|
+
pry (0.9.12.6)
|
69
|
+
coderay (~> 1.0)
|
70
|
+
method_source (~> 0.8)
|
71
|
+
slop (~> 3.4)
|
72
|
+
rack (1.5.2)
|
73
|
+
rack-flash3 (1.0.5)
|
74
|
+
rack
|
75
|
+
rack-protection (1.5.3)
|
76
|
+
rack
|
77
|
+
rack-test (0.6.2)
|
78
|
+
rack (>= 1.0)
|
79
|
+
rails-observers (0.1.2)
|
80
|
+
activemodel (~> 4.0)
|
81
|
+
rake (10.3.2)
|
82
|
+
redis (3.0.7)
|
83
|
+
redis-namespace (1.4.1)
|
84
|
+
redis (~> 3.0.4)
|
85
|
+
resque (1.22.0)
|
86
|
+
multi_json (~> 1.0)
|
87
|
+
redis-namespace (~> 1.0)
|
88
|
+
sinatra (>= 0.9.2)
|
89
|
+
vegas (~> 0.1.2)
|
90
|
+
shopify_api (3.2.4)
|
91
|
+
activeresource (>= 3.0.0)
|
92
|
+
pry (>= 0.9.12.6)
|
93
|
+
thor (~> 0.18.1)
|
94
|
+
sinatra (1.4.5)
|
95
|
+
rack (~> 1.4)
|
96
|
+
rack-protection (~> 1.4)
|
97
|
+
tilt (~> 1.3, >= 1.3.4)
|
98
|
+
sinatra-activerecord (2.0.2)
|
99
|
+
activerecord (>= 3.2)
|
100
|
+
sinatra (~> 1.0)
|
101
|
+
sinatra-redis (0.3.0)
|
102
|
+
redis
|
103
|
+
sinatra (>= 0.9.4)
|
104
|
+
sinatra-twitter-bootstrap (2.3.3)
|
105
|
+
slop (3.5.0)
|
106
|
+
sqlite3 (1.3.9)
|
107
|
+
thor (0.18.1)
|
108
|
+
thread_safe (0.3.4)
|
109
|
+
tilt (1.4.1)
|
110
|
+
tzinfo (1.2.1)
|
111
|
+
thread_safe (~> 0.1)
|
112
|
+
vegas (0.1.11)
|
113
|
+
rack (>= 1.0.0)
|
114
|
+
|
115
|
+
PLATFORMS
|
116
|
+
ruby
|
117
|
+
|
118
|
+
DEPENDENCIES
|
119
|
+
activesupport
|
120
|
+
attr_encrypted
|
121
|
+
byebug
|
122
|
+
fakeweb
|
123
|
+
foreman
|
124
|
+
json
|
125
|
+
mocha
|
126
|
+
omniauth-shopify-oauth2
|
127
|
+
pg
|
128
|
+
rack-flash3
|
129
|
+
rack-test
|
130
|
+
rake
|
131
|
+
resque (~> 1.22.0)
|
132
|
+
shopify_api
|
133
|
+
sinatra
|
134
|
+
sinatra-activerecord
|
135
|
+
sinatra-redis
|
136
|
+
sinatra-twitter-bootstrap
|
137
|
+
sqlite3
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 Kevin Hughes
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
shopify-sinatra-app
|
2
|
+
===================
|
3
|
+
|
4
|
+
"A classy shopify app"
|
5
|
+
|
6
|
+
|
7
|
+
Getting Started
|
8
|
+
---------------
|
9
|
+
|
10
|
+
Install the gem:
|
11
|
+
|
12
|
+
```
|
13
|
+
gem install shopify-sinatra-app
|
14
|
+
```
|
15
|
+
|
16
|
+
or build from source
|
17
|
+
|
18
|
+
```
|
19
|
+
gem build shopify-sinatra-app.gemspec
|
20
|
+
gem install shopify-sinatra-app-X.X.X.gem
|
21
|
+
```
|
22
|
+
|
23
|
+
To create a new app use the generator:
|
24
|
+
|
25
|
+
```
|
26
|
+
shopify-sinatra-app-generator new <your new app name>
|
27
|
+
```
|
28
|
+
|
29
|
+
This will create a new skeleton shopify-sinatra-app. The generator will create several default files for you rather than having them bundled in the sinatra extension - its worthwhile to read this section to understand what each of these files is for.
|
30
|
+
|
31
|
+
`config/app.yml` --> Some important config information is contained in this file.
|
32
|
+
* scope: The scope of your app (what your app can access from the Shopify store once installed, e.g. read_products), this will be read by your app and used when your app is installed.
|
33
|
+
|
34
|
+
* uninstall_webhook: Initially an uninstall webhook is also defined in this file, although without a proper url, this file is a good place to define objects that need to be created on Shopify when your app is installed like webhooks, fulfillment and carrier services.
|
35
|
+
|
36
|
+
`config/database.yml` --> The database config for active record. Initially this is setup to use sqlite3 for development and testing which you may want to change to mimic your production database.
|
37
|
+
|
38
|
+
`config.ru` --> Rackup file - describes how to run a rack based app
|
39
|
+
|
40
|
+
`Gemfile` --> manages the dependencies of the app
|
41
|
+
|
42
|
+
`lib/app.rb` --> This file is the skeleton app file. More details on how to use the methods provided by this extension are given in the following section.
|
43
|
+
|
44
|
+
`Procfile` --> Specific for deploying to Heroku, this file tells heroku how to run the app
|
45
|
+
|
46
|
+
`public/icon.png` --> This icon file is used by the Shopify Embedded App SKD and is shown in the menu bar of your embedded app
|
47
|
+
|
48
|
+
`Rakefile` --> includes some helper methods etc for running and managing the app. Standard for ruby based projects
|
49
|
+
|
50
|
+
`views/layouts/appliction.erb` --> This is the layout file that all templates will use unless otherwise specified. It sets up some defaults for using the Shopify Embedded App SDK and Twitter Bootstrap for styling
|
51
|
+
|
52
|
+
`views/_top_bar.erb` --> This is a partial view that describes the top bar inside a Shopify Embedded App. It also has some code to *forward* flash messages to the Embedded App SKD
|
53
|
+
|
54
|
+
`views/*` --> The other views used by the app. You'll probably make a lot of changes to home.erb and install.erb to customize the experience for your app
|
55
|
+
|
56
|
+
|
57
|
+
### Setting the app to use your Shopify API credentials
|
58
|
+
|
59
|
+
You'll need to create a Shopify Partner Account and a new application. You can make an account [here](http://www.shopify.ca/partners) and see this [tutorial](http://docs.shopify.com/api/the-basics/getting-started) for creating a new application.
|
60
|
+
|
61
|
+
After creating your new application you need to create a `.env` file and add the following lines:
|
62
|
+
|
63
|
+
```
|
64
|
+
SHOPIFY_API_KEY=<your api key>
|
65
|
+
SHOPIFY_SHARED_SECRET=<your shared secret>
|
66
|
+
SECRET=<generate a random string to encrypt credentials with>
|
67
|
+
```
|
68
|
+
|
69
|
+
|
70
|
+
Shopify::Methods
|
71
|
+
----------------
|
72
|
+
|
73
|
+
`shopify_session` - The main method of the framework, most of your routes will use this method to acquire a valid shopify session and then perform api calls to Shopfiy. The method simply takes a block of code and makes the shop_name available to you after activating an api session. Here is an example endpoint that displays products:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
get '/products.json' do
|
77
|
+
shopify_session do |shop_name|
|
78
|
+
products = ShopifyAPI::Product.all(limit: 5)
|
79
|
+
products.to_json
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
`webhook_session` - This method is for an endpoint that recieves a webhook from Shopify. Webhooks are a great way to keep your app in sync with a shop's data without polling. You can read more about webhooks [here](http://docs.shopify.com/api/tutorials/using-webhooks). This method also takes a block of code and makes the shop and webhook data as a hash available (note only works for json webhooks, don't use xml). Here is an example that listens to a order creation webhook:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
post '/order.json' do
|
88
|
+
webhook_session do |shop, params|
|
89
|
+
# do something with the data
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
`webhook_job` - Its impossible to control the flow of webhooks to your app from Shopify especially if a larger store installs your app or if a shop has a flash sale. To prevent your app from getting overloaded with webhook requests it is usually a good idea to process webhooks in a background queue and return a 200 to Shopify immediately. This method provides this functionality using redis and resque. This method takes the name of a job class whose perform method expects a `shop_name` and the webhook data as a hash. The session method is useful for prototpying and experimenting but production apps should use `webhook_job`. Here is an example:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
post '/order.json' do
|
98
|
+
webhook_job(OrderWebhookJob)
|
99
|
+
end
|
100
|
+
|
101
|
+
class OrderWebhookJob
|
102
|
+
@queue = :default
|
103
|
+
|
104
|
+
def self.perform(shop_name, params)
|
105
|
+
# do something with the data
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
`install` - This is a private method provided with the framework that gets called when the app is authorized for the first time. You should fill this method in with anything you need to initialize on install, for example webhooks and services on shopify or any other database models you have created specific to a shop.
|
111
|
+
|
112
|
+
`uninstall` - This method gets called when your app recieves an uninstall webhook from shopify. You should override this method in your class and do any appropriate clean up when the app is removed from a shop.
|
113
|
+
|
114
|
+
`logout` - This method clears the current session data in the app
|
115
|
+
|
116
|
+
`current_shop` - Returns the name of the current shop
|
117
|
+
|
118
|
+
`base_url` - This returns the url of the app
|
119
|
+
|
120
|
+
|
121
|
+
Deploying
|
122
|
+
---------
|
123
|
+
|
124
|
+
This template was created with deploying to Heroku in mind. Heroku is a great cloud based app hosting provider that makes it incredibly easy to get an application into a product environment. To create a new heroku application download the [Heroku Toolbelt](https://devcenter.heroku.com/articles/quickstart) and create a new application:
|
125
|
+
|
126
|
+
```
|
127
|
+
heroku apps:create <your new app name>
|
128
|
+
```
|
129
|
+
|
130
|
+
You will also need to add the following (free) add-ons to your new Heroku app:
|
131
|
+
|
132
|
+
```
|
133
|
+
heroku addons:add heroku-postgresql
|
134
|
+
heroku addons:add rediscloud
|
135
|
+
```
|
136
|
+
|
137
|
+
and make sure you have at least 1 dyno for web and resque:
|
138
|
+
|
139
|
+
```
|
140
|
+
heroku scale web=1 resque=1
|
141
|
+
```
|
142
|
+
|
143
|
+
Note - if you are not using any background queue for processing webhooks then you do not need the redis add-on or the resque dyno so you can set it to 0.
|
144
|
+
|
145
|
+
Contributing
|
146
|
+
------------
|
147
|
+
|
148
|
+
PRs welcome!
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
working_dir = Dir.pwd
|
5
|
+
|
6
|
+
spec = Gem::Specification.find_by_name("shopify-sinatra-app")
|
7
|
+
gem_root = spec.gem_dir
|
8
|
+
gem_lib = gem_root + "/lib"
|
9
|
+
|
10
|
+
generator_dir = gem_lib + "/generator"
|
11
|
+
|
12
|
+
if ARGV.length < 2
|
13
|
+
puts "Usage:\n./shopify-sinatra-app new <app_name>"
|
14
|
+
else
|
15
|
+
app_name = ARGV[1]
|
16
|
+
app_dir = working_dir + "/" + app_name
|
17
|
+
|
18
|
+
begin
|
19
|
+
Dir.mkdir(app_dir)
|
20
|
+
|
21
|
+
FileUtils.cp_r(generator_dir + "/config", app_dir + "/config")
|
22
|
+
FileUtils.cp_r(generator_dir + "/db", app_dir + "/db")
|
23
|
+
FileUtils.cp_r(generator_dir + "/lib", app_dir + "/lib")
|
24
|
+
FileUtils.cp_r(generator_dir + "/public", app_dir + "/public")
|
25
|
+
FileUtils.cp_r(generator_dir + "/views", app_dir + "/views")
|
26
|
+
|
27
|
+
FileUtils.cp(generator_dir + "/config.ru", app_dir + "/config.ru")
|
28
|
+
FileUtils.cp(generator_dir + "/Procfile", app_dir + "/Procfile")
|
29
|
+
FileUtils.cp(generator_dir + "/Rakefile", app_dir + "/Rakefile")
|
30
|
+
FileUtils.cp(generator_dir + "/Gemfile", app_dir + "/Gemfile")
|
31
|
+
FileUtils.cp(generator_dir + "/README.md", app_dir + "/README.md")
|
32
|
+
|
33
|
+
Dir.chdir(app_dir)
|
34
|
+
|
35
|
+
pipe = IO.popen("bundle install")
|
36
|
+
while (line = pipe.gets)
|
37
|
+
print line
|
38
|
+
end
|
39
|
+
|
40
|
+
pipe = IO.popen("bundle exec rake db:migrate")
|
41
|
+
while (line = pipe.gets)
|
42
|
+
print line
|
43
|
+
end
|
44
|
+
|
45
|
+
rescue Errno::EEXIST => e
|
46
|
+
puts "App directory alread exists, pick a new app name or delete the existing folder"
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
ruby "2.0.0"
|
3
|
+
|
4
|
+
gem 'sinatra'
|
5
|
+
gem 'sinatra-redis'
|
6
|
+
gem 'sinatra-activerecord'
|
7
|
+
gem 'sinatra-twitter-bootstrap', require: 'sinatra/twitter-bootstrap'
|
8
|
+
gem 'rack-flash3', require: 'rack-flash'
|
9
|
+
gem 'activesupport'
|
10
|
+
gem 'attr_encrypted'
|
11
|
+
gem 'foreman'
|
12
|
+
gem 'rake'
|
13
|
+
|
14
|
+
gem 'resque', '~> 1.22.0'
|
15
|
+
|
16
|
+
gem 'shopify-sinatra-app'
|
17
|
+
gem 'omniauth-shopify-oauth2'
|
18
|
+
gem 'shopify_api'
|
19
|
+
|
20
|
+
group :production do
|
21
|
+
gem 'pg'
|
22
|
+
end
|
23
|
+
|
24
|
+
group :development do
|
25
|
+
gem 'sqlite3'
|
26
|
+
gem 'rack-test'
|
27
|
+
gem 'fakeweb'
|
28
|
+
gem 'mocha', require: false
|
29
|
+
gem 'byebug'
|
30
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
web: bundle exec rackup config.ru -p $PORT
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'sinatra/activerecord/rake'
|
2
|
+
require 'resque/tasks'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require './lib/app'
|
5
|
+
|
6
|
+
task :server do
|
7
|
+
pipe = IO.popen("bundle exec rackup config.ru -p 4567")
|
8
|
+
while (line = pipe.gets)
|
9
|
+
print line
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
task :deploy do
|
14
|
+
pipe = IO.popen("git push heroku master --force")
|
15
|
+
while (line = pipe.gets)
|
16
|
+
print line
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
task :clear do
|
21
|
+
Rake::Task["clear_products"].invoke
|
22
|
+
Rake::Task["clear_charities"].invoke
|
23
|
+
Rake::Task["clear_shops"].invoke
|
24
|
+
end
|
25
|
+
|
26
|
+
task :clear_shops do
|
27
|
+
Shop.delete_all
|
28
|
+
end
|
29
|
+
|
30
|
+
task :clear_charities do
|
31
|
+
Charity.delete_all
|
32
|
+
end
|
33
|
+
|
34
|
+
task :clear_products do
|
35
|
+
Product.delete_all
|
36
|
+
end
|
37
|
+
|
38
|
+
task :creds2heroku do
|
39
|
+
Bundler.with_clean_env {
|
40
|
+
api_key = `sed -n '1p' .env`
|
41
|
+
shared_secret = `sed -n '2p' .env`
|
42
|
+
secret = `sed -n '3p' .env`
|
43
|
+
|
44
|
+
`heroku config:set #{api_key}`
|
45
|
+
`heroku config:set #{shared_secret}`
|
46
|
+
`heroku config:set #{secret}`
|
47
|
+
}
|
48
|
+
end
|
File without changes
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'sinatra/shopify-sinatra-app'
|
2
|
+
|
3
|
+
class SinatraApp < Sinatra::Base
|
4
|
+
register Sinatra::Shopify
|
5
|
+
|
6
|
+
get '/' do
|
7
|
+
# your app's Home page
|
8
|
+
shopify_session do |shop_name|
|
9
|
+
@shop = Shop.find_by(:name => shop_name)
|
10
|
+
@products = ShopifyAPI::Product.all(limit: 5)
|
11
|
+
erb :home
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def install
|
18
|
+
# setup any webhooks or services you need when your app is installed
|
19
|
+
shopify_session do |shop_name|
|
20
|
+
params = YAML.load(File.read("config/app.yml"))
|
21
|
+
|
22
|
+
# create the uninstall webhook
|
23
|
+
uninstall_webhook = ShopifyAPI::Webhook.new(params["uninstall_webhook"])
|
24
|
+
unless ShopifyAPI::Webhook.find(:all).include?(uninstall_webhook)
|
25
|
+
uninstall_webhook.save
|
26
|
+
end
|
27
|
+
end
|
28
|
+
redirect '/'
|
29
|
+
end
|
30
|
+
|
31
|
+
def uninstall
|
32
|
+
# remove data for a shop when they uninstall your app
|
33
|
+
webhook_session do |shop, params|
|
34
|
+
shop.destroy
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
Binary file
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
|
3
|
+
ShopifyApp.ready(function(){
|
4
|
+
ShopifyApp.Bar.initialize({
|
5
|
+
icon: 'icon.png'
|
6
|
+
});
|
7
|
+
|
8
|
+
<% if flash[:notice] %>
|
9
|
+
ShopifyApp.flashNotice("<%= flash[:notice] %>");
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<% if flash[:error] %>
|
13
|
+
ShopifyApp.flashError("<%= flash[:error] %>");
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
});
|
17
|
+
</script>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%= erb :"_top_bar", :layout => false, :locals => locals %>
|
2
|
+
|
3
|
+
<h3>Shopify Sinatra App</h3>
|
4
|
+
<p>Welcome!</p>
|
5
|
+
|
6
|
+
<h3>Products</h3>
|
7
|
+
<table class="table table-striped table-hover">
|
8
|
+
<tbody>
|
9
|
+
<% @products.each do |product| %>
|
10
|
+
<tr>
|
11
|
+
<td>
|
12
|
+
<a href=<%="https://#{@shop.name}/admin/products/#{product.product_id}"%> target="_blank"> <%= product.product_id %> </a>
|
13
|
+
</td>
|
14
|
+
</tr>
|
15
|
+
<% end %>
|
16
|
+
</tbody>
|
17
|
+
</table>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<h1>Shopify Sinatra App</h1>
|
2
|
+
|
3
|
+
<h2>Install This App</h2>
|
4
|
+
<h2><small>This app requires you to login to start using it.</small></h2>
|
5
|
+
|
6
|
+
<form action="/login" method="post">
|
7
|
+
<input type="url" name="shop" placeholder="Shop URL">
|
8
|
+
<input type="submit" value="Install App" />
|
9
|
+
</form>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<script src="https://cdn.shopify.com/s/assets/external/app.js"></script>
|
5
|
+
<script type="text/javascript">
|
6
|
+
ShopifyApp.init({
|
7
|
+
apiKey: "<%= SinatraApp.settings.api_key %>",
|
8
|
+
shopOrigin: "<%= current_shop_url %>",
|
9
|
+
debug: true
|
10
|
+
});
|
11
|
+
</script>
|
12
|
+
|
13
|
+
<%= bootstrap_assets %>
|
14
|
+
</head>
|
15
|
+
|
16
|
+
<body>
|
17
|
+
<div class="container">
|
18
|
+
<%= yield %>
|
19
|
+
</div>
|
20
|
+
</body>
|
21
|
+
</html>
|
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/redis'
|
3
|
+
require 'sinatra/activerecord'
|
4
|
+
require 'sinatra/twitter-bootstrap'
|
5
|
+
|
6
|
+
require 'resque'
|
7
|
+
require 'rack-flash'
|
8
|
+
require 'attr_encrypted'
|
9
|
+
require 'active_support/all'
|
10
|
+
|
11
|
+
require 'shopify_api'
|
12
|
+
require 'omniauth-shopify-oauth2'
|
13
|
+
|
14
|
+
module Sinatra
|
15
|
+
module Shopify
|
16
|
+
|
17
|
+
module Methods
|
18
|
+
def install
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def uninstall
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def logout
|
27
|
+
session[:shopify] = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def base_url
|
31
|
+
@base_url ||= "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def current_shop
|
35
|
+
session[:shopify][:shop] if session.has_key?(:shopify)
|
36
|
+
end
|
37
|
+
|
38
|
+
def current_shop_url
|
39
|
+
"https://#{current_shop}" if current_shop
|
40
|
+
end
|
41
|
+
|
42
|
+
def shopify_session(&blk)
|
43
|
+
if !session.has_key?(:shopify)
|
44
|
+
get_session
|
45
|
+
elsif params[:shop].present? && session[:shopify][:shop] != sanitize_shop_param(params)
|
46
|
+
logout
|
47
|
+
get_session
|
48
|
+
else
|
49
|
+
shop_name = session[:shopify][:shop]
|
50
|
+
token = session[:shopify][:token]
|
51
|
+
|
52
|
+
api_session = ShopifyAPI::Session.new(shop_name, token)
|
53
|
+
ShopifyAPI::Base.activate_session(api_session)
|
54
|
+
|
55
|
+
yield shop_name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def webhook_session(&blk)
|
60
|
+
return unless verify_shopify_webhook
|
61
|
+
|
62
|
+
shop_name = request.env['HTTP_X_SHOPIFY_SHOP_DOMAIN']
|
63
|
+
shop = Shop.find_by(:name => shop_name)
|
64
|
+
|
65
|
+
if shop.present?
|
66
|
+
params = ActiveSupport::JSON.decode(request.body.read.to_s)
|
67
|
+
api_session = ShopifyAPI::Session.new(shop_name, shop.token)
|
68
|
+
ShopifyAPI::Base.activate_session(api_session)
|
69
|
+
|
70
|
+
yield shop, params
|
71
|
+
|
72
|
+
status 200
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def webhook_job(jobKlass)
|
77
|
+
return unless verify_shopify_webhook
|
78
|
+
|
79
|
+
shop_name = request.env['HTTP_X_SHOPIFY_SHOP_DOMAIN']
|
80
|
+
params = ActiveSupport::JSON.decode(request.body.read.to_s)
|
81
|
+
|
82
|
+
Resque.enqueue(jobKlass, shop_name, params)
|
83
|
+
|
84
|
+
status 200
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def get_session
|
90
|
+
shop_name = sanitize_shop_param(params)
|
91
|
+
shop = Shop.find_by(:name => shop_name)
|
92
|
+
|
93
|
+
return_to = request.env["sinatra.route"].split(' ').last
|
94
|
+
|
95
|
+
if shop.present?
|
96
|
+
session[:shopify] ||= {}
|
97
|
+
session[:shopify][:shop] = shop.name
|
98
|
+
session[:shopify][:token] = shop.token
|
99
|
+
redirect return_to
|
100
|
+
else
|
101
|
+
authenticate(return_to)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def authenticate(return_to = '/')
|
106
|
+
if shop_name = sanitize_shop_param(params)
|
107
|
+
redirect_url = "/auth/shopify?shop=#{shop_name}&return_to=#{base_url}#{return_to}"
|
108
|
+
fullpage_redirect_to redirect_url
|
109
|
+
else
|
110
|
+
redirect "/install"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def fullpage_redirect_to(redirect_url)
|
115
|
+
@fullpage_redirect_to = redirect_url
|
116
|
+
|
117
|
+
erb "<script type='text/javascript'>
|
118
|
+
window.top.location.href = '<%= @fullpage_redirect_to %>';
|
119
|
+
</script>", :layout => false
|
120
|
+
end
|
121
|
+
|
122
|
+
def sanitize_shop_param(params)
|
123
|
+
return unless params[:shop].present?
|
124
|
+
name = params[:shop].to_s.strip
|
125
|
+
name += '.myshopify.com' if !name.include?("myshopify.com") && !name.include?(".")
|
126
|
+
name.gsub!('https://', '')
|
127
|
+
name.gsub!('http://', '')
|
128
|
+
|
129
|
+
u = URI("http://#{name}")
|
130
|
+
u.host.ends_with?(".myshopify.com") ? u.host : nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def verify_shopify_webhook
|
134
|
+
data = request.body.read.to_s
|
135
|
+
digest = OpenSSL::Digest::Digest.new('sha256')
|
136
|
+
calculated_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, app.settings.shared_secret, data)).strip
|
137
|
+
request.body.rewind
|
138
|
+
|
139
|
+
calculated_hmac == request.env['HTTP_X_SHOPIFY_HMAC_SHA256']
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.registered(app)
|
144
|
+
app.helpers Shopify::Methods
|
145
|
+
app.register Sinatra::ActiveRecordExtension
|
146
|
+
app.register Sinatra::Twitter::Bootstrap::Assets
|
147
|
+
|
148
|
+
app.set :database_file, File.expand_path("config/database.yml")
|
149
|
+
app.set :views, File.expand_path("views")
|
150
|
+
app.set :public_folder, File.expand_path("public")
|
151
|
+
app.set :erb, :layout => :'layouts/application'
|
152
|
+
app.set :protection, :except => :frame_options
|
153
|
+
|
154
|
+
app.enable :sessions
|
155
|
+
app.enable :inline_templates
|
156
|
+
|
157
|
+
app.set :scope, YAML.load(File.read(File.expand_path("config/app.yml")))["scope"]
|
158
|
+
|
159
|
+
app.set :api_key, ENV['SHOPIFY_API_KEY']
|
160
|
+
app.set :shared_secret, ENV['SHOPIFY_SHARED_SECRET']
|
161
|
+
app.set :secret, ENV['SECRET']
|
162
|
+
|
163
|
+
app.use Rack::Flash, :sweep => true
|
164
|
+
app.use Rack::MethodOverride
|
165
|
+
app.use Rack::Session::Cookie, :key => '#{base_url}.session',
|
166
|
+
:path => '/',
|
167
|
+
:secret => app.settings.secret,
|
168
|
+
:expire_after => 2592000
|
169
|
+
|
170
|
+
app.set :redis_url, ENV["REDISCLOUD_URL"] || "redis://localhost:6379/"
|
171
|
+
redis_uri = URI.parse(app.settings.redis_url)
|
172
|
+
Resque.redis = ::Redis.new(:host => redis_uri.host,
|
173
|
+
:port => redis_uri.port,
|
174
|
+
:password => redis_uri.password)
|
175
|
+
Resque.redis.namespace = "resque"
|
176
|
+
app.set :redis, app.settings.redis_url
|
177
|
+
|
178
|
+
app.use OmniAuth::Builder do
|
179
|
+
provider :shopify,
|
180
|
+
app.settings.api_key,
|
181
|
+
app.settings.shared_secret,
|
182
|
+
|
183
|
+
:scope => app.settings.scope,
|
184
|
+
|
185
|
+
:setup => lambda { |env|
|
186
|
+
params = Rack::Utils.parse_query(env['QUERY_STRING'])
|
187
|
+
site_url = "https://#{params['shop']}"
|
188
|
+
env['omniauth.strategy'].options[:client_options][:site] = site_url
|
189
|
+
}
|
190
|
+
end
|
191
|
+
|
192
|
+
ShopifyAPI::Session.setup({:api_key => app.settings.api_key,
|
193
|
+
:secret => app.settings.shared_secret})
|
194
|
+
|
195
|
+
app.get '/install' do
|
196
|
+
erb :install, :layout => false
|
197
|
+
end
|
198
|
+
|
199
|
+
# endpoint for the app/uninstall webhook
|
200
|
+
app.post '/uninstall.json' do
|
201
|
+
uninstall
|
202
|
+
end
|
203
|
+
|
204
|
+
app.post '/login' do
|
205
|
+
authenticate
|
206
|
+
end
|
207
|
+
|
208
|
+
app.get '/logout' do
|
209
|
+
logout
|
210
|
+
redirect '/install'
|
211
|
+
end
|
212
|
+
|
213
|
+
app.get '/auth/shopify/callback' do
|
214
|
+
shop_name = params["shop"]
|
215
|
+
token = request.env['omniauth.auth']['credentials']['token']
|
216
|
+
|
217
|
+
session[:shopify] ||= {}
|
218
|
+
session[:shopify][:shop] = shop_name
|
219
|
+
session[:shopify][:token] = token
|
220
|
+
|
221
|
+
if Shop.where(:name => shop_name).blank?
|
222
|
+
Shop.create(:name => shop_name, :token => token)
|
223
|
+
install
|
224
|
+
end
|
225
|
+
|
226
|
+
return_to = env['omniauth.params']['return_to']
|
227
|
+
redirect return_to
|
228
|
+
end
|
229
|
+
|
230
|
+
app.get '/auth/failure' do
|
231
|
+
erb "<h1>Authentication Failed:</h1>
|
232
|
+
<h3>message:<h3> <pre>#{params}</pre>", :layout => false
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
class Shop < ActiveRecord::Base
|
238
|
+
|
239
|
+
def self.secret
|
240
|
+
@secret ||= ENV['SECRET']
|
241
|
+
end
|
242
|
+
|
243
|
+
attr_encrypted :token, :key => secret, :attribute => 'token_encrypted'
|
244
|
+
validates_presence_of :name, :token
|
245
|
+
end
|
246
|
+
|
247
|
+
register Shopify
|
248
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'shopify-sinatra-app'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
|
5
|
+
s.summary = "A classy shopify app"
|
6
|
+
s.description = "A Sinatra extension for building Shopify Apps. Akin to the shopify_app gem but for Sinatra"
|
7
|
+
|
8
|
+
s.authors = ["Kevin Hughes"]
|
9
|
+
s.email = "kevin.hughes@shopify.com"
|
10
|
+
s.homepage = "https://github.com/pickle27/shopify-sinatra-app/"
|
11
|
+
s.license = 'MIT'
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.executables << 'shopify-sinatra-app-generator'
|
15
|
+
|
16
|
+
s.add_runtime_dependency 'sinatra'
|
17
|
+
s.add_runtime_dependency 'sinatra-redis'
|
18
|
+
s.add_runtime_dependency 'sinatra-activerecord'
|
19
|
+
s.add_runtime_dependency 'sinatra-twitter-bootstrap'
|
20
|
+
s.add_runtime_dependency 'rack-flash3'
|
21
|
+
s.add_runtime_dependency 'activesupport'
|
22
|
+
s.add_runtime_dependency 'attr_encrypted'
|
23
|
+
|
24
|
+
s.add_runtime_dependency 'resque', '~> 1.22.0'
|
25
|
+
|
26
|
+
s.add_runtime_dependency 'shopify_api'
|
27
|
+
s.add_runtime_dependency 'omniauth-shopify-oauth2'
|
28
|
+
|
29
|
+
s.add_development_dependency 'rake'
|
30
|
+
s.add_development_dependency 'sqlite3'
|
31
|
+
s.add_development_dependency 'rack-test'
|
32
|
+
s.add_development_dependency 'fakeweb'
|
33
|
+
s.add_development_dependency 'mocha'
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,279 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shopify-sinatra-app
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kevin Hughes
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sinatra
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sinatra-redis
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sinatra-activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sinatra-twitter-bootstrap
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rack-flash3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activesupport
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: attr_encrypted
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: resque
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.22.0
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.22.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: shopify_api
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: omniauth-shopify-oauth2
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rake
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: sqlite3
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: rack-test
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: fakeweb
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: mocha
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
description: A Sinatra extension for building Shopify Apps. Akin to the shopify_app
|
224
|
+
gem but for Sinatra
|
225
|
+
email: kevin.hughes@shopify.com
|
226
|
+
executables:
|
227
|
+
- shopify-sinatra-app-generator
|
228
|
+
extensions: []
|
229
|
+
extra_rdoc_files: []
|
230
|
+
files:
|
231
|
+
- ".gitignore"
|
232
|
+
- Gemfile
|
233
|
+
- Gemfile.lock
|
234
|
+
- LICENSE
|
235
|
+
- README.md
|
236
|
+
- bin/shopify-sinatra-app-generator
|
237
|
+
- lib/generator/Gemfile
|
238
|
+
- lib/generator/Procfile
|
239
|
+
- lib/generator/README.md
|
240
|
+
- lib/generator/Rakefile
|
241
|
+
- lib/generator/config.ru
|
242
|
+
- lib/generator/config/app.yml
|
243
|
+
- lib/generator/config/database.yml
|
244
|
+
- lib/generator/db/.keep
|
245
|
+
- lib/generator/db/migrate/20140413221328_create_shops.rb
|
246
|
+
- lib/generator/db/migrate/20140414042317_add_index_to_shops.rb
|
247
|
+
- lib/generator/lib/app.rb
|
248
|
+
- lib/generator/public/icon.png
|
249
|
+
- lib/generator/views/_top_bar.erb
|
250
|
+
- lib/generator/views/home.erb
|
251
|
+
- lib/generator/views/install.erb
|
252
|
+
- lib/generator/views/layouts/application.erb
|
253
|
+
- lib/sinatra/shopify-sinatra-app.rb
|
254
|
+
- shopify-sinatra-app.gemspec
|
255
|
+
homepage: https://github.com/pickle27/shopify-sinatra-app/
|
256
|
+
licenses:
|
257
|
+
- MIT
|
258
|
+
metadata: {}
|
259
|
+
post_install_message:
|
260
|
+
rdoc_options: []
|
261
|
+
require_paths:
|
262
|
+
- lib
|
263
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
264
|
+
requirements:
|
265
|
+
- - ">="
|
266
|
+
- !ruby/object:Gem::Version
|
267
|
+
version: '0'
|
268
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
269
|
+
requirements:
|
270
|
+
- - ">="
|
271
|
+
- !ruby/object:Gem::Version
|
272
|
+
version: '0'
|
273
|
+
requirements: []
|
274
|
+
rubyforge_project:
|
275
|
+
rubygems_version: 2.0.14
|
276
|
+
signing_key:
|
277
|
+
specification_version: 4
|
278
|
+
summary: A classy shopify app
|
279
|
+
test_files: []
|