passkit 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.example.env +6 -0
- data/README.md +124 -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 +58 -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 +49 -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/membership.png +0 -0
- data/docs/passkit_environment_variables.md +48 -0
- data/docs/step1.png +0 -0
- data/docs/step2.png +0 -0
- data/docs/step3.png +0 -0
- data/docs/step4.png +0 -0
- data/docs/step5.png +0 -0
- 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 +124 -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/payload_generator.rb +18 -0
- data/lib/passkit/url_encrypt.rb +3 -3
- data/lib/passkit/url_generator.rb +20 -0
- data/lib/passkit/version.rb +1 -1
- data/lib/passkit.rb +37 -3
- data/passkit-0.2.0.gem +0 -0
- 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 +140 -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: 37dfbf2ceba4423e2a1eba28d94aa8f5fbf676c011429efb8759af2f7fd8c477
|
4
|
+
data.tar.gz: 5909695a00d94a28da5f4ae1ee318a1d7d7320c8599646457a408b7c2a780af0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 637340fef65ed2655a1e8bedaf9d86a5ece533782bbd6c3c92a26d2e49d5153de1be57e27977994b13b63e1f47a2b39b30e481c57539771b8c06b3f2db4b6691
|
7
|
+
data.tar.gz: 5350a3793c359fbbf00d197541e7b51577c93c46c68068f8e0f7cb852ff46f6c9d1f60e4a3a8c00fc66a52c82c6e95b94b768ff602b1c188bb887f3128214821
|
data/.example.env
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
PASSKIT_WEB_SERVICE_HOST=https://localhost:3000
|
2
|
+
PASSKIT_CERTIFICATE_KEY=path/to/certificate.key
|
3
|
+
PASSKIT_PRIVATE_P12_CERTIFICATE=path/to/certificate.p12
|
4
|
+
PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE=path/to/AppleWWDRCA.cer
|
5
|
+
PASSKIT_APPLE_TEAM_IDENTIFIER=XXXXXXXXXX
|
6
|
+
PASSKIT_PASS_TYPE_IDENTIFIER=pass.com.example.pass
|
data/README.md
CHANGED
@@ -1,6 +1,31 @@
|
|
1
|
-
# Passkit
|
1
|
+
# <img src="./docs/wallet.png" alt="Goboony" height="50"/> Passkit
|
2
2
|
|
3
|
-
|
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 necessary to distribute Wallet Passes in pkpass format, 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 necessary ActiveRecord models.
|
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):**
|
19
|
+
|
20
|
+
* Full tests coverage: we are working on it!
|
21
|
+
* A fancy dashboard: our dashboard is really really simple right now. Pull requests are welcome!
|
22
|
+
* Push notifications: this is the most wanted feature I believe. Pull requests are welcome!
|
23
|
+
* Google Wallet integration: we use https://walletpasses.io/ on Android to read .pkpass format.
|
24
|
+
|
25
|
+
## Apple documentation
|
26
|
+
|
27
|
+
* [Apple Wallet Passes](https://developer.apple.com/documentation/walletpasses)
|
28
|
+
* [Send Push Notifications](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns)
|
4
29
|
|
5
30
|
## Installation
|
6
31
|
|
@@ -18,9 +43,97 @@ Or install it yourself as:
|
|
18
43
|
|
19
44
|
$ gem install passkit
|
20
45
|
|
46
|
+
Run the initializer:
|
47
|
+
|
48
|
+
$ rails g passkit:install
|
49
|
+
|
50
|
+
that will generate the migrations and the initializer file.
|
51
|
+
|
52
|
+
Mount the engine in your `config/routes.rb`:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
mount Passkit::Engine => '/passkit', as: 'passkit'
|
56
|
+
```
|
57
|
+
|
58
|
+
and run `bin/rails db:migrate`.
|
59
|
+
|
60
|
+
### Setup environment variables
|
61
|
+
|
62
|
+
If you followed the installation steps, you already saw that Passkit provides
|
63
|
+
you the tables and ActiveRecord models, and also an engine with the necessary APIs already implemented.
|
64
|
+
|
65
|
+
Now is your turn. Before proceeding, you need to set these ENV variables:
|
66
|
+
* `PASSKIT_WEB_SERVICE_HOST`
|
67
|
+
* `PASSKIT_CERTIFICATE_KEY`
|
68
|
+
* `PASSKIT_PRIVATE_P12_CERTIFICATE`
|
69
|
+
* `PASSKIT_APPLE_INTERMEDIATE_CERTIFICATE`
|
70
|
+
* `PASSKIT_APPLE_TEAM_IDENTIFIER`
|
71
|
+
* `PASSKIT_PASS_TYPE_IDENTIFIER`
|
72
|
+
|
73
|
+
We have a [specific guide on how to get all these](docs/passkit_environment_variables.md), please follow it.
|
74
|
+
You cannot start using this library without these variables set, and we cannot do the work for you.
|
75
|
+
|
21
76
|
## Usage
|
22
77
|
|
23
|
-
|
78
|
+
If you followed the installation steps and you have the ENV variables set, we can start looking at what is provided for you.
|
79
|
+
|
80
|
+
### Dashboard
|
81
|
+
|
82
|
+
Head to `http://localhost:3000/passkit/previews` and you will see a first `ExampleStoreCard` available for download.
|
83
|
+
You can click on the button and you will obtain a `.pkpass` file that you can simply open or install on your phone.
|
84
|
+
The dashboard has also a view for logs, and a view for emitted passes.
|
85
|
+
|
86
|
+
### Mailer Helpers
|
87
|
+
|
88
|
+
If you use mailer previews, you can create the following file in `spec/mailers/previews/passkit/example_mailer_preview.rb`:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
module Passkit
|
92
|
+
class ExampleMailerPreview < ActionMailer::Preview
|
93
|
+
def example_email
|
94
|
+
Passkit::ExampleMailer.example_email
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
and head to `http://localhost:3000/rails/mailers/` to see an example of email with links to download the Wallet Pass.
|
101
|
+
Please check the source code of [ExampleMailer](app/mailers/passkit/example_mailer.rb) to see how to distribute your own Wallet Passes.
|
102
|
+
|
103
|
+
### Example Passes
|
104
|
+
|
105
|
+
Please check the source code of the [ExampleStoreCard](lib/passkit/example_store_card.rb) to see how to create your own Wallet Passes.
|
106
|
+
|
107
|
+
Again, looking at these examples, is the easiest way to get started.
|
108
|
+
|
109
|
+
### Create your own Wallet Pass
|
110
|
+
|
111
|
+
You can create your own Wallet Passes by creating a new class that inherits from `Passkit::BasePass` and
|
112
|
+
defining the methods that you want to override.
|
113
|
+
|
114
|
+
You can define colors, fields and texts. You can also define the logo and the background image.
|
115
|
+
|
116
|
+
You should place the images in a 'private/passkit/<your_downcased_passname>' folder.
|
117
|
+
There is a [dummy app in the gem](test/dummy) that you can use to check how to create your own Wallet Passes.
|
118
|
+
|
119
|
+
### Serve your Wallet Pass
|
120
|
+
|
121
|
+
Use the [Passkit::UrlGenerator](lib/passkit/url_generator.rb) to generate the URL to serve your Wallet Pass.
|
122
|
+
You can initialize it with:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
Passkit::UrlGenerator.new(pass_class: Passkit::MyPass, generator: User.find(1))
|
126
|
+
```
|
127
|
+
|
128
|
+
and then use `.android` or `.ios` to get the URL to serve the Wallet Pass.
|
129
|
+
Again, check the example mailer included in the gem to see how to use it.
|
130
|
+
|
131
|
+
## Debug issues
|
132
|
+
|
133
|
+
* On Mac, open the `Console.app` to view the errors.
|
134
|
+
* Check the logs on http://localhost:3000/passkit/logs
|
135
|
+
* In case of error "The passTypeIdentifier or teamIdentifier provided may not match your certificate,
|
136
|
+
or the certificate trust chain could not be verified." the certificate (p12) might be expired.
|
24
137
|
|
25
138
|
## Development
|
26
139
|
|
@@ -30,7 +143,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
30
143
|
|
31
144
|
## Contributing
|
32
145
|
|
33
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
146
|
+
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
147
|
|
35
148
|
## License
|
36
149
|
|
@@ -38,4 +151,10 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
38
151
|
|
39
152
|
## Code of Conduct
|
40
153
|
|
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/
|
154
|
+
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).
|
155
|
+
|
156
|
+
## Credits
|
157
|
+
|
158
|
+
* <a href="https://www.flaticon.com/free-icons/credit-card" title="credit card icons">Credit card icons created by Iconfromus - Flaticon</a>
|
159
|
+
|
160
|
+
* https://www.sitepoint.com/whats-in-your-wallet-handling-ios-passbook-with-ruby/
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,58 @@
|
|
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(" ")&.last
|
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.last_update.strftime("%Y-%m-%d %H:%M:%S")
|
30
|
+
if request.headers["If-Modified-Since"].nil? ||
|
31
|
+
(pass.last_update.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 = nil
|
49
|
+
if payload[:generator_class].present? && payload[:generator_id].present?
|
50
|
+
generator_class = payload[:generator_class].constantize
|
51
|
+
generator = generator_class.find(payload[:generator_id])
|
52
|
+
end
|
53
|
+
Passkit::Factory.create_pass(payload[:pass_class], generator)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
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(" ")&.last
|
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,49 @@
|
|
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
|
+
|
45
|
+
def last_update
|
46
|
+
instance.last_update || updated_at
|
47
|
+
end
|
48
|
+
end
|
49
|
+
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>
|