passkit 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +121 -5
- data/app/assets/images/passkit/add_to_apple_wallet_de.png +0 -0
- data/app/assets/images/passkit/add_to_apple_wallet_en.png +0 -0
- data/app/assets/images/passkit/add_to_apple_wallet_fr.png +0 -0
- data/app/assets/images/passkit/add_to_apple_wallet_it.png +0 -0
- data/app/assets/images/passkit/add_to_walletpass_de.png +0 -0
- data/app/assets/images/passkit/add_to_walletpass_en.png +0 -0
- data/app/assets/images/passkit/add_to_walletpass_fr.png +0 -0
- data/app/assets/images/passkit/add_to_walletpass_it.png +0 -0
- data/app/controllers/passkit/api/v1/logs_controller.rb +14 -0
- data/app/controllers/passkit/api/v1/passes_controller.rb +55 -0
- data/app/controllers/passkit/api/v1/registrations_controller.rb +115 -0
- data/app/controllers/passkit/logs_controller.rb +9 -0
- data/app/controllers/passkit/passes_controller.rb +4 -19
- data/app/controllers/passkit/previews_controller.rb +13 -0
- data/app/mailers/passkit/example_mailer.rb +8 -0
- data/{lib → app/models}/passkit/device.rb +2 -2
- data/{lib → app/models}/passkit/log.rb +0 -0
- data/app/models/passkit/pass.rb +45 -0
- data/app/models/passkit/registration.rb +6 -0
- data/app/views/layouts/passkit/application.html.erb +16 -0
- data/app/views/passkit/example_mailer/example_email.html.erb +15 -0
- data/app/views/passkit/logs/index.html.erb +22 -0
- data/app/views/passkit/passes/index.html.erb +32 -0
- data/app/views/passkit/previews/index.html.erb +21 -0
- data/app/views/shared/passkit/_navigation.html.erb +5 -0
- data/config/routes.rb +17 -7
- data/docs/wallet.png +0 -0
- data/lib/generators/passkit/install_generator.rb +26 -0
- data/lib/generators/templates/create_passkit_tables.rb.tt +32 -0
- data/lib/generators/templates/passkit.rb +3 -0
- data/lib/passkit/base_pass.rb +120 -0
- data/lib/passkit/example_store_card/icon.png +0 -0
- data/lib/passkit/example_store_card/icon@2x.png +0 -0
- data/lib/passkit/example_store_card/icon@3x.png +0 -0
- data/lib/passkit/example_store_card/logo.png +0 -0
- data/lib/passkit/example_store_card.rb +110 -0
- data/lib/passkit/factory.rb +2 -2
- data/lib/passkit/generator.rb +30 -22
- data/lib/passkit/url_encrypt.rb +3 -3
- data/lib/passkit/url_generator.rb +27 -0
- data/lib/passkit/version.rb +1 -1
- data/lib/passkit.rb +36 -3
- data/sig/lib/passkit/encryptable_payload.rbs +5 -0
- data/sig/lib/passkit/factory.rbs +5 -0
- data/sig/lib/passkit/generator.rbs +8 -0
- data/sig/lib/passkit/generator_object.rbs +5 -0
- data/sig/lib/passkit/pass.rbs +5 -0
- data/sig/lib/passkit/url_encrypt.rbs +6 -0
- data/sig/lib/passkit/url_generator.rbs +9 -0
- metadata +116 -8
- data/lib/passkit/pass.rb +0 -8
- data/sig/passkit.rbs +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abe44c75069e0fc397f9a1f7155dd811a0341de723223ca3fdfbe7543b9a8a6c
|
4
|
+
data.tar.gz: c8a36754d91a1dd241064cdf0e5b3d3defbf5df54d13c3135bf56b5dc8564b19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1137d58cf9f2b560a0363ab2c14868eca913960bd1eea70e3737dcdc5cff3411de06a72988ffce0cb910d14ab30de68502da3542bbe48d4f17e262b1c3c48f8e
|
7
|
+
data.tar.gz: 4d6e5d66d65705b8b01c84820559b4244be5ad1f9ea71ec58b9c86fd133933bea766783c192250571cc80ad28c4b90c0e57e22affc8bbdfeff8c9ded6cbc3827
|
data/README.md
CHANGED
@@ -1,6 +1,27 @@
|
|
1
|
-
# Passkit
|
1
|
+
# <img src="./docs/wallet.png" alt="Goboony" height="50"/> Passkit
|
2
|
+
|
3
|
+
Your out-of-the-box solution to start serving Wallet Passes in your Ruby On Rails application.
|
4
|
+
|
5
|
+
Do you have a QRCode or a Barcode anywhere in your app that you want to distribute as Wallet Pass, compatible for iOS and Android? Look no further!
|
6
|
+
|
7
|
+
This gem provides everything that is necessary, and gives you all the steps to follow for what we cannot provide.
|
8
|
+
|
9
|
+
We provide:
|
10
|
+
|
11
|
+
* A (not yet) fancy dashboard to manage your passes, registered devices and logs
|
12
|
+
* All API endpoints to serve your passes: create, register, update, unregister, etc...
|
13
|
+
* All ActiveRecord models necessary
|
14
|
+
* A BasePass model that you can extend to create your own passes
|
15
|
+
* Some helpers to generate the necessary URLs, so that you can include them in the emails
|
16
|
+
* Examples for everything
|
17
|
+
|
18
|
+
We don't provide yet (WIP):
|
19
|
+
|
20
|
+
* Full tests coverage. We are working on it!
|
21
|
+
* A fancy dashboard. Pull requests are welcome!
|
22
|
+
* Push notifications. Pull requests are welcome!
|
23
|
+
* Google Wallet passes. We use https://walletpasses.io/ on Android to read .pkpass format.
|
2
24
|
|
3
|
-
Generate Wallet Passes for iOS.
|
4
25
|
|
5
26
|
## Installation
|
6
27
|
|
@@ -18,9 +39,100 @@ Or install it yourself as:
|
|
18
39
|
|
19
40
|
$ gem install passkit
|
20
41
|
|
42
|
+
Run the initializer:
|
43
|
+
|
44
|
+
$ rails g passkit:install
|
45
|
+
|
46
|
+
that will generate the migrations and the initializer file.
|
47
|
+
|
48
|
+
Mount the engine in your routes.rb:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
mount Passkit::Engine => '/passkit', as: 'passkit'
|
52
|
+
```
|
53
|
+
|
54
|
+
and run `rails db:migrate`.
|
55
|
+
|
21
56
|
## Usage
|
22
57
|
|
23
|
-
|
58
|
+
If you followed the installation steps, you already saw that Passkit provides
|
59
|
+
you the tables and ActiveRecord models, and also an engine the necessary APIs already implemented.
|
60
|
+
|
61
|
+
Now is your turn. Before proceeding, you need to set these ENV variables:
|
62
|
+
* `PASSKIT_WEB_SERVICE_HOST`
|
63
|
+
* `PASSKIT_CERTIFICATE_KEY`
|
64
|
+
* `PASSKIT_PRIVATE_P12_CERTIFICATE`
|
65
|
+
* `PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE`
|
66
|
+
* `PASSKIT_APPLE_TEAM_IDENTIFIER`
|
67
|
+
* `PASSKIT_PASS_TYPE_IDENTIFIER`
|
68
|
+
|
69
|
+
We have a [specific guide on how to get all these](docs/passkit_environment_variables.md), please follow it.
|
70
|
+
You cannot do any of the following steps without these variables set, and we cannot do the work for you there.
|
71
|
+
|
72
|
+
### Check what is available
|
73
|
+
|
74
|
+
If you followed the installation steps and you have the ENV variables set, we can start looking at what is provided for you.
|
75
|
+
|
76
|
+
Head to `http://localhost:3000/passkit/previews` and you will see a first ExampleStoreCard available for download.
|
77
|
+
You can click on the button and you will obtain a `.pkpass` file that you can simply open or install on your phone.
|
78
|
+
|
79
|
+
If you use mailer previews, you can create the following file in `spec/mailers/previews/passkit/example_mailer_preview.rb`:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
module Passkit
|
83
|
+
class ExampleMailerPreview < ActionMailer::Preview
|
84
|
+
def example_email
|
85
|
+
Passkit::ExampleMailer.example_email
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
and head to `http://localhost:3000/rails/mailers/` to see an example of email with links to download the Wallet Pass.
|
92
|
+
|
93
|
+
Please check the source code of the [ExampleStoreCard](lib/passkit/example_store_card.rb) and [ExampleMailer](app/mailers/passkit/example_mailer.rb) to see how to create and distribute your own Wallet Passes.
|
94
|
+
Looking at the examples, is the easiest way to get started.
|
95
|
+
|
96
|
+
### Create your own Wallet Pass
|
97
|
+
|
98
|
+
You can create your own Wallet Passes by creating a new class that inherits from `Passkit::BasePass` and defining the methods that you want to override.
|
99
|
+
You can define colors, fields and texts. You can also define the logo and the background image.
|
100
|
+
|
101
|
+
You can place the images in a 'private/passkit/<your_downcased_passname>' folder.
|
102
|
+
There is a [dummy app in the gem](test/dummy) that you can use to check how to create your own Wallet Passes.
|
103
|
+
|
104
|
+
### Serve your Wallet Pass
|
105
|
+
|
106
|
+
Use the [Passkit::UrlGenerator](lib/passkit/url_generator.rb) to generate the URL to serve your Wallet Pass. You can initialize it with:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
Passkit::UrlGenerator.new(pass_class: Passkit::MyPass, generator: User.find(1))
|
110
|
+
```
|
111
|
+
|
112
|
+
and then use `.android` or `.ios` to get the URL to serve the Wallet Pass.
|
113
|
+
Again, check the example mailer included in the gem to see how to use it.
|
114
|
+
|
115
|
+
## Debug issues
|
116
|
+
|
117
|
+
* On Mac, open the `Console.app` to view the errors.
|
118
|
+
* Check the logs on http://localhost:3000/passkit/logs
|
119
|
+
* In case of error "The passTypeIdentifier or teamIdentifier provided may not match your certificate,
|
120
|
+
or the certificate trust chain could not be verified." the certificate (p12) might be expired.
|
121
|
+
|
122
|
+
|
123
|
+
* Setup ngrok and start it
|
124
|
+
* Configure your `HOST: 'https://xxx.ngrok.io'` on `config/application.yml` (make sure is https!)
|
125
|
+
* Set `config.hosts = nil` in `config/environments/development.rb`
|
126
|
+
* Head to the `/rails/mailers/` and preview an email containing a pass
|
127
|
+
* Download the pkpass and open it
|
128
|
+
|
129
|
+
|
130
|
+
Here are the rough steps to generate a new one:
|
131
|
+
* Access https://developer.apple.com/account/resources/identifiers/passTypeId/edit/F578W7LDT9/pass.ch.renuo.loyalty.
|
132
|
+
* Click "Create Certificate".
|
133
|
+
* Follow the guide to generate a new Certificate Signing Request and use operations@renuo.ch as email address and Renuo AG as organization name.
|
134
|
+
* Convert the certificate to p12 using Keychain Access and re-use the same password.
|
135
|
+
* Re-upload the new certificate to the servers.
|
24
136
|
|
25
137
|
## Development
|
26
138
|
|
@@ -30,7 +142,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
30
142
|
|
31
143
|
## Contributing
|
32
144
|
|
33
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
145
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/coorasse/passkit. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/coorasse/passkit/blob/master/CODE_OF_CONDUCT.md).
|
34
146
|
|
35
147
|
## License
|
36
148
|
|
@@ -38,4 +150,8 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
38
150
|
|
39
151
|
## Code of Conduct
|
40
152
|
|
41
|
-
Everyone interacting in the Passkit project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
153
|
+
Everyone interacting in the Passkit project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/coorasse/passkit/blob/master/CODE_OF_CONDUCT.md).
|
154
|
+
|
155
|
+
## Credits
|
156
|
+
|
157
|
+
<a href="https://www.flaticon.com/free-icons/credit-card" title="credit card icons">Credit card icons created by Iconfromus - Flaticon</a>
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Passkit
|
2
|
+
module Api
|
3
|
+
module V1
|
4
|
+
class PassesController < ActionController::API
|
5
|
+
before_action :decrypt_payload, only: :create
|
6
|
+
|
7
|
+
def create
|
8
|
+
send_file(fetch_pass(@payload))
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return If request is authorized, returns HTTP status 200 with a payload of the pass data.
|
12
|
+
# @return If the request is not authorized, returns HTTP status 401.
|
13
|
+
# @return Otherwise, returns the appropriate standard HTTP status.
|
14
|
+
def show
|
15
|
+
authentication_token = request.headers["Authorization"]&.split(" ") & [1]
|
16
|
+
unless authentication_token.present?
|
17
|
+
render json: {}, status: :unauthorized
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
pass = Pass.find_by(serial_number: params[:serial_number], authentication_token: authentication_token)
|
22
|
+
unless pass
|
23
|
+
render json: {}, status: :unauthorized
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
pass_output_path = Passkit::Generator.new(pass).generate_and_sign
|
28
|
+
|
29
|
+
response.headers["last-modified"] = pass.generator.updated_at.strftime("%Y-%m-%d %H:%M:%S")
|
30
|
+
if request.headers["If-Modified-Since"].nil? ||
|
31
|
+
(pass.generator.updated_at.to_i > Time.zone.parse(request.headers["If-Modified-Since"]).to_i)
|
32
|
+
send_file(pass_output_path)
|
33
|
+
else
|
34
|
+
head :not_modified
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def decrypt_payload
|
41
|
+
@payload = Passkit::UrlEncrypt.decrypt(params[:payload])
|
42
|
+
if DateTime.parse(@payload[:valid_until]).past?
|
43
|
+
head :not_found
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_pass(payload)
|
48
|
+
generator_class = payload[:generator_class].constantize
|
49
|
+
generator = generator_class.find(payload[:generator_id])
|
50
|
+
Passkit::Factory.create_pass(generator, payload[:pass_class])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Passkit
|
2
|
+
module Api
|
3
|
+
module V1
|
4
|
+
# TODO: check with authentication_token
|
5
|
+
# This Class Implements the Apple PassKit API
|
6
|
+
# @see Apple: https://developer.apple.com/library/archive/documentation/PassKit/Reference/PassKit_WebService/WebService.html
|
7
|
+
# @see Android: https://walletpasses.io/developer/
|
8
|
+
class RegistrationsController < ActionController::API
|
9
|
+
before_action :load_pass, only: %i[create destroy]
|
10
|
+
before_action :load_device, only: %i[show]
|
11
|
+
|
12
|
+
# @return If the serial number is already registered for this device, returns HTTP status 200.
|
13
|
+
# @return If registration succeeds, returns HTTP status 201.
|
14
|
+
# @return If the request is not authorized, returns HTTP status 401.
|
15
|
+
# @return Otherwise, returns the appropriate standard HTTP status.
|
16
|
+
def create
|
17
|
+
if @pass.devices.find_by(identifier: params[:device_id])
|
18
|
+
render json: {}, status: :ok
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
register_device
|
23
|
+
render json: {}, status: :created
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return If there are matching passes, returns HTTP status 200
|
27
|
+
# with a JSON dictionary with the following keys and values:
|
28
|
+
# lastUpdated (string): The current modification tag.
|
29
|
+
# serialNumbers (array of strings): The serial numbers of the matching passes.
|
30
|
+
# @return If there are no matching passes, returns HTTP status 204.
|
31
|
+
# @return Otherwise, returns the appropriate standard HTTP status
|
32
|
+
def show
|
33
|
+
if @device.nil?
|
34
|
+
render json: {}, status: :not_found
|
35
|
+
return
|
36
|
+
end
|
37
|
+
|
38
|
+
passes = fetch_registered_passes
|
39
|
+
if passes.none?
|
40
|
+
render json: {}, status: :no_content
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
render json: updatable_passes(passes).to_json
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return If disassociation succeeds, returns HTTP status 200.
|
48
|
+
# @return If the request is not authorized, returns HTTP status 401.
|
49
|
+
# @return Otherwise, returns the appropriate standard HTTP status.
|
50
|
+
def destroy
|
51
|
+
devices = @pass.devices.where(device_id: params[:device_id],
|
52
|
+
pass_type_id: params[:pass_type_id])
|
53
|
+
if devices.any?
|
54
|
+
devices.delete_all
|
55
|
+
render json: {}, status: :ok
|
56
|
+
else
|
57
|
+
render json: {}, status: :unauthorized
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def load_pass
|
64
|
+
authentication_token = request.headers["Authorization"]&.split(" ") & [1]
|
65
|
+
unless authentication_token.present?
|
66
|
+
render json: {}, status: :unauthorized
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
@pass = Pass.find_by(serial_number: params[:serial_number], authentication_token: authentication_token)
|
71
|
+
unless @pass
|
72
|
+
render json: {}, status: :unauthorized
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def load_device
|
77
|
+
@device = Passkit::Device.find_by(identifier: params[:device_id])
|
78
|
+
end
|
79
|
+
|
80
|
+
def register_device
|
81
|
+
@pass.devices.create!(identifier: params[:device_id], push_token: push_token)
|
82
|
+
end
|
83
|
+
|
84
|
+
def fetch_registered_passes
|
85
|
+
passes = @device.passes
|
86
|
+
|
87
|
+
if params[:passesUpdatedSince]&.present?
|
88
|
+
passes.all.filter { |a| a.generator.updated_at >= Date.parse(params[:passesUpdatedSince]) }
|
89
|
+
else
|
90
|
+
passes
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def updatable_passes(passes)
|
95
|
+
{lastUpdated: Time.zone.now, serialNumbers: passes.pluck(:serial_number)}
|
96
|
+
end
|
97
|
+
|
98
|
+
# TODO: add authentication_token
|
99
|
+
# The value is the word ApplePass, followed by a space
|
100
|
+
# The value is the word AndroidPass (instead of ApplePass), followed by a space
|
101
|
+
def authentication_token
|
102
|
+
""
|
103
|
+
end
|
104
|
+
|
105
|
+
def push_token
|
106
|
+
return unless request&.body
|
107
|
+
|
108
|
+
request.body.rewind
|
109
|
+
json_body = JSON.parse(request.body.read)
|
110
|
+
json_body["pushToken"]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -1,24 +1,9 @@
|
|
1
1
|
module Passkit
|
2
|
-
class PassesController
|
3
|
-
|
4
|
-
before_action :decrypt_payload, only: :create
|
2
|
+
class PassesController < ActionController::Base
|
3
|
+
layout "passkit/application"
|
5
4
|
|
6
|
-
def
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def decrypt_payload
|
13
|
-
@payload = Passkit::UrlEncrypt.decrypt(params[:payload])
|
14
|
-
if DateTime.parse(@payload[:valid_until]).past?
|
15
|
-
head :not_found
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def fetch_pass(payload)
|
20
|
-
generator = payload[:generator_type].constantize.find(payload[:generator_id])
|
21
|
-
Passkit::Factory.create_pass(generator)
|
5
|
+
def index
|
6
|
+
@passes = Passkit::Pass.order(created_at: :desc).includes(:devices).first(100)
|
22
7
|
end
|
23
8
|
end
|
24
9
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Passkit
|
2
|
+
class PreviewsController < ActionController::Base
|
3
|
+
layout "passkit/application"
|
4
|
+
|
5
|
+
def index
|
6
|
+
end
|
7
|
+
|
8
|
+
def show
|
9
|
+
builder = Passkit.configuration.available_passes[params[:class_name]]
|
10
|
+
send_file Factory.create_pass(builder.call, params[:class_name].constantize)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Passkit
|
2
2
|
class Device < ActiveRecord::Base
|
3
|
-
validates_uniqueness_of :
|
3
|
+
validates_uniqueness_of :identifier
|
4
4
|
validates_uniqueness_of :push_token
|
5
5
|
|
6
|
-
has_many :registrations
|
6
|
+
has_many :registrations, foreign_key: :passkit_device_id
|
7
7
|
has_many :passes, through: :registrations
|
8
8
|
end
|
9
9
|
end
|
File without changes
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Passkit
|
2
|
+
class Pass < ActiveRecord::Base
|
3
|
+
validates_uniqueness_of :serial_number
|
4
|
+
validates_presence_of :klass
|
5
|
+
|
6
|
+
belongs_to :generator, polymorphic: true, optional: true
|
7
|
+
has_many :registrations, foreign_key: :passkit_pass_id
|
8
|
+
has_many :devices, through: :registrations
|
9
|
+
|
10
|
+
delegate :file_name,
|
11
|
+
:pass_path,
|
12
|
+
:language,
|
13
|
+
:format_version,
|
14
|
+
:apple_team_identifier,
|
15
|
+
:foreground_color,
|
16
|
+
:background_color,
|
17
|
+
:web_service_url,
|
18
|
+
:barcode,
|
19
|
+
:voided,
|
20
|
+
:organization_name,
|
21
|
+
:description,
|
22
|
+
:logo_text,
|
23
|
+
:locations,
|
24
|
+
:pass_type_identifier,
|
25
|
+
:pass_type,
|
26
|
+
:header_fields,
|
27
|
+
:primary_fields,
|
28
|
+
:secondary_fields,
|
29
|
+
:auxiliary_fields,
|
30
|
+
:back_fields,
|
31
|
+
to: :instance
|
32
|
+
|
33
|
+
before_validation on: :create do
|
34
|
+
self.authentication_token ||= SecureRandom.hex
|
35
|
+
loop do
|
36
|
+
self.serial_number = SecureRandom.uuid
|
37
|
+
break unless self.class.exists?(serial_number: serial_number)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def instance
|
42
|
+
@instance ||= klass.constantize.new(generator)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Passkit Previews</title>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
|
+
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
|
10
|
+
<%= yield %>
|
11
|
+
|
12
|
+
<footer>
|
13
|
+
Made with ❤️ by <a href="https://github.com/sponsors/coorasse/">coorasse</a>.
|
14
|
+
</footer>
|
15
|
+
</body>
|
16
|
+
</html>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<p>
|
2
|
+
Here is an example of an email message that contains a link to Download a Wallet Pass.
|
3
|
+
</p>
|
4
|
+
<p>
|
5
|
+
<a href="<%=@passkit_url_generator.ios %>">
|
6
|
+
<%=image_tag 'passkit/add_to_apple_wallet_en.png', style: 'width: 150px;' %>
|
7
|
+
Download a iOS Wallet Pass
|
8
|
+
</a>
|
9
|
+
</p>
|
10
|
+
<p>
|
11
|
+
<a href="<%=@passkit_url_generator.android %>">
|
12
|
+
<%=image_tag 'https://walletpasses.io/badges/badge_web_black_en@3x.png', style: 'width: 150px;' %>
|
13
|
+
Download an Android Wallet Pass
|
14
|
+
</a>
|
15
|
+
</p>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<header>
|
2
|
+
<h1>Passkit Logs</h1>
|
3
|
+
<%= render 'shared/passkit/navigation' %>
|
4
|
+
</header>
|
5
|
+
|
6
|
+
<main>
|
7
|
+
<h2>Last 100 log entries</h2>
|
8
|
+
<table>
|
9
|
+
<thead>
|
10
|
+
<th>Timestamp</th>
|
11
|
+
<th>Content</th>
|
12
|
+
</thead>
|
13
|
+
<tbody>
|
14
|
+
<% @logs.each do |log| %>
|
15
|
+
<tr>
|
16
|
+
<td><%= I18n.l(log.created_at) %></td>
|
17
|
+
<td><%= log.content.sub(/^\[.*\]/, '') %></td>
|
18
|
+
</tr>
|
19
|
+
<% end %>
|
20
|
+
</tbody>
|
21
|
+
</table>
|
22
|
+
</main>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<header>
|
2
|
+
<h1>Passkit Passes</h1>
|
3
|
+
<%= render 'shared/passkit/navigation' %>
|
4
|
+
</header>
|
5
|
+
|
6
|
+
<main>
|
7
|
+
<h2>Last 100 passes and registered devices</h2>
|
8
|
+
<table>
|
9
|
+
<thead>
|
10
|
+
<th>Created at</th>
|
11
|
+
<th>Generator</th>
|
12
|
+
<th>Pass Class</th>
|
13
|
+
<th>Devices</th>
|
14
|
+
</thead>
|
15
|
+
<tbody>
|
16
|
+
<% @passes.each do |pass| %>
|
17
|
+
<tr>
|
18
|
+
<td><%= I18n.l(pass.created_at) %></td>
|
19
|
+
<td><%= pass.generator.to_s %></td>
|
20
|
+
<td><%= pass.klass %></td>
|
21
|
+
<td>
|
22
|
+
<% pass.devices.each do |device| %>
|
23
|
+
<div>
|
24
|
+
<%= device.push_token %>
|
25
|
+
</div>
|
26
|
+
<% end %>
|
27
|
+
</td>
|
28
|
+
</tr>
|
29
|
+
<% end %>
|
30
|
+
</tbody>
|
31
|
+
</table>
|
32
|
+
</main>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<header>
|
2
|
+
<h1>Passkit Previews</h1>
|
3
|
+
<%= render 'shared/passkit/navigation' %>
|
4
|
+
</header>
|
5
|
+
<main>
|
6
|
+
<p>Here you can generate a Pass from the ones available. Configure your available passes with:</p>
|
7
|
+
<pre>
|
8
|
+
Passkit.configure do |config|
|
9
|
+
config.available_passes['Passkit::YourPass'] = -> { FactoryBot.create(:user) }
|
10
|
+
end
|
11
|
+
</pre>
|
12
|
+
<p>Where you specify the class name as key and a function to generate a builder,
|
13
|
+
that will be passed used to generte the Pass</p>
|
14
|
+
|
15
|
+
<% Passkit.configuration.available_passes.each do |pass_class_name, _builder_function| %>
|
16
|
+
<article>
|
17
|
+
<h2><%= pass_class_name %></h2>
|
18
|
+
<%= link_to 'Generate and download', preview_path(pass_class_name) %>
|
19
|
+
</article>
|
20
|
+
<% end %>
|
21
|
+
</main>
|
data/config/routes.rb
CHANGED
@@ -1,12 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Passkit::Engine.routes.draw do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
scope :api, constraints: {pass_type_id: /.*/} do
|
5
|
+
scope :v1 do
|
6
|
+
resources :devices, only: [] do
|
7
|
+
post "registrations/:pass_type_id/:serial_number" => "api/v1/registrations#create", :as => :register
|
8
|
+
delete "registrations/:pass_type_id/:serial_number" => "api/v1/registrations#destroy", :as => :unregister
|
9
|
+
get "registrations/:pass_type_id" => "api/v1/registrations#show", :as => :registrations
|
10
|
+
end
|
11
|
+
get "passes/:pass_type_id/:serial_number" => "api/v1/passes#show", :as => :pass
|
12
|
+
get "passes/:payload", to: "api/v1/passes#create", as: :passes_api
|
13
|
+
post "log" => "api/v1/logs#create", :as => :log
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
unless Rails.env.production?
|
18
|
+
resources :previews, only: [:index, :show], param: :class_name
|
19
|
+
resources :logs, only: [:index]
|
20
|
+
resources :passes, only: [:index]
|
8
21
|
end
|
9
|
-
get 'passes/:pass_type_id/:serial_number' => 'passes#show', as: :pass
|
10
|
-
get 'passes/:payload', to: 'passes#create', as: :passes
|
11
|
-
post 'log' => 'logs#create'
|
12
22
|
end
|
data/docs/wallet.png
ADDED
Binary file
|