passkit 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +121 -5
  3. data/app/assets/images/passkit/add_to_apple_wallet_de.png +0 -0
  4. data/app/assets/images/passkit/add_to_apple_wallet_en.png +0 -0
  5. data/app/assets/images/passkit/add_to_apple_wallet_fr.png +0 -0
  6. data/app/assets/images/passkit/add_to_apple_wallet_it.png +0 -0
  7. data/app/assets/images/passkit/add_to_walletpass_de.png +0 -0
  8. data/app/assets/images/passkit/add_to_walletpass_en.png +0 -0
  9. data/app/assets/images/passkit/add_to_walletpass_fr.png +0 -0
  10. data/app/assets/images/passkit/add_to_walletpass_it.png +0 -0
  11. data/app/controllers/passkit/api/v1/logs_controller.rb +14 -0
  12. data/app/controllers/passkit/api/v1/passes_controller.rb +55 -0
  13. data/app/controllers/passkit/api/v1/registrations_controller.rb +115 -0
  14. data/app/controllers/passkit/logs_controller.rb +9 -0
  15. data/app/controllers/passkit/passes_controller.rb +4 -19
  16. data/app/controllers/passkit/previews_controller.rb +13 -0
  17. data/app/mailers/passkit/example_mailer.rb +8 -0
  18. data/{lib → app/models}/passkit/device.rb +2 -2
  19. data/{lib → app/models}/passkit/log.rb +0 -0
  20. data/app/models/passkit/pass.rb +45 -0
  21. data/app/models/passkit/registration.rb +6 -0
  22. data/app/views/layouts/passkit/application.html.erb +16 -0
  23. data/app/views/passkit/example_mailer/example_email.html.erb +15 -0
  24. data/app/views/passkit/logs/index.html.erb +22 -0
  25. data/app/views/passkit/passes/index.html.erb +32 -0
  26. data/app/views/passkit/previews/index.html.erb +21 -0
  27. data/app/views/shared/passkit/_navigation.html.erb +5 -0
  28. data/config/routes.rb +17 -7
  29. data/docs/wallet.png +0 -0
  30. data/lib/generators/passkit/install_generator.rb +26 -0
  31. data/lib/generators/templates/create_passkit_tables.rb.tt +32 -0
  32. data/lib/generators/templates/passkit.rb +3 -0
  33. data/lib/passkit/base_pass.rb +120 -0
  34. data/lib/passkit/example_store_card/icon.png +0 -0
  35. data/lib/passkit/example_store_card/icon@2x.png +0 -0
  36. data/lib/passkit/example_store_card/icon@3x.png +0 -0
  37. data/lib/passkit/example_store_card/logo.png +0 -0
  38. data/lib/passkit/example_store_card.rb +110 -0
  39. data/lib/passkit/factory.rb +2 -2
  40. data/lib/passkit/generator.rb +30 -22
  41. data/lib/passkit/url_encrypt.rb +3 -3
  42. data/lib/passkit/url_generator.rb +27 -0
  43. data/lib/passkit/version.rb +1 -1
  44. data/lib/passkit.rb +36 -3
  45. data/sig/lib/passkit/encryptable_payload.rbs +5 -0
  46. data/sig/lib/passkit/factory.rbs +5 -0
  47. data/sig/lib/passkit/generator.rbs +8 -0
  48. data/sig/lib/passkit/generator_object.rbs +5 -0
  49. data/sig/lib/passkit/pass.rbs +5 -0
  50. data/sig/lib/passkit/url_encrypt.rbs +6 -0
  51. data/sig/lib/passkit/url_generator.rbs +9 -0
  52. metadata +116 -8
  53. data/lib/passkit/pass.rb +0 -8
  54. data/sig/passkit.rbs +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14879f088666efc426ff4d2861589f526991129dd83f91a70d7bf8867f64071c
4
- data.tar.gz: d52fca69a103bb5fd958502449e61ad194ba41928b8d1337c7f715cd799add79
3
+ metadata.gz: abe44c75069e0fc397f9a1f7155dd811a0341de723223ca3fdfbe7543b9a8a6c
4
+ data.tar.gz: c8a36754d91a1dd241064cdf0e5b3d3defbf5df54d13c3135bf56b5dc8564b19
5
5
  SHA512:
6
- metadata.gz: 37a9f00cc34e6f272ebb811a199fb9457eb750297b616bfd487cb091b48dad0fae0d5782674c50a70033eadd803767bdccc11b53f6e6b4149b5ecbf5c619bf54
7
- data.tar.gz: ac193511be72f80a214a3e4bf4dd149430fc2682b4381368e6352e19635010298c623fe1b6c51b9b6f1323a062f023f1c2c45e53123a4fc801e2dfe9785be80d
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
- TODO: Write usage instructions here
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/[USERNAME]/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/[USERNAME]/passkit/blob/master/CODE_OF_CONDUCT.md).
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/[USERNAME]/passkit/blob/master/CODE_OF_CONDUCT.md).
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>
@@ -0,0 +1,14 @@
1
+ module Passkit
2
+ module Api
3
+ module V1
4
+ class LogsController < ActionController::API
5
+ def create
6
+ params[:logs].each do |message|
7
+ Log.create!(content: message)
8
+ end
9
+ render json: {}, status: :ok
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ module Passkit
2
+ class LogsController < ActionController::Base
3
+ layout "passkit/application"
4
+
5
+ def index
6
+ @logs = Passkit::Log.order(created_at: :desc).first(100)
7
+ end
8
+ end
9
+ end
@@ -1,24 +1,9 @@
1
1
  module Passkit
2
- class PassesController < ActionController::Base
3
- skip_before_action :verify_authenticity_token
4
- before_action :decrypt_payload, only: :create
2
+ class PassesController < ActionController::Base
3
+ layout "passkit/application"
5
4
 
6
- def create
7
- send_file(fetch_pass(@payload))
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
@@ -0,0 +1,8 @@
1
+ module Passkit
2
+ class ExampleMailer < ActionMailer::Base
3
+ def example_email
4
+ @passkit_url_generator = Passkit::UrlGenerator.new(Passkit::ExampleStoreCard, nil)
5
+ mail(to: "passkit@example.com", subject: "Here is an example of a passkit email")
6
+ end
7
+ end
8
+ end
@@ -1,9 +1,9 @@
1
1
  module Passkit
2
2
  class Device < ActiveRecord::Base
3
- validates_uniqueness_of :device_identifier
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,6 @@
1
+ module Passkit
2
+ class Registration < ActiveRecord::Base
3
+ belongs_to :device, foreign_key: :passkit_device_id
4
+ belongs_to :pass, foreign_key: :passkit_pass_id
5
+ end
6
+ 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>
@@ -0,0 +1,5 @@
1
+ <nav>
2
+ <%=link_to 'Logs', logs_path %>
3
+ <%=link_to 'Passes', passes_path %>
4
+ <%=link_to 'Passes Previews', previews_path %>
5
+ </nav>
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
- resources :devices, only: [] do
5
- post 'registrations/:pass_type_id/:serial_number' => 'registrations#create', as: :register
6
- delete 'registrations/:pass_type_id/:serial_number' => 'registrations#destroy', as: :unregister
7
- get 'registrations/:pass_type_id' => 'registrations#show', as: :registrations
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