rails_api_keys-ui 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +26 -0
  3. data/LICENSE +21 -0
  4. data/README.md +130 -0
  5. data/app/assets/stylesheets/rails_api_keys/ui.css +138 -0
  6. data/app/controllers/rails_api_keys/ui/api_keys_controller.rb +152 -0
  7. data/app/controllers/rails_api_keys/ui/application_controller.rb +10 -0
  8. data/app/helpers/rails_api_keys/ui/api_keys_helper.rb +43 -0
  9. data/app/javascript/rails_api_keys/ui/controllers/clipboard_controller.js +19 -0
  10. data/app/javascript/rails_api_keys/ui/controllers/modal_controller.js +43 -0
  11. data/app/policies/rails_api_keys/ui/api_key_policy.rb +49 -0
  12. data/app/views/rails_api_keys/ui/api_keys/_form_modal.html.erb +49 -0
  13. data/app/views/rails_api_keys/ui/api_keys/_list.html.erb +28 -0
  14. data/app/views/rails_api_keys/ui/api_keys/_panel.html.erb +27 -0
  15. data/app/views/rails_api_keys/ui/api_keys/_reveal_modal.html.erb +48 -0
  16. data/app/views/rails_api_keys/ui/api_keys/index.html.erb +5 -0
  17. data/config/locales/en.yml +42 -0
  18. data/config/routes.rb +4 -0
  19. data/lib/generators/rails_api_keys_ui/controllers/controllers_generator.rb +17 -0
  20. data/lib/generators/rails_api_keys_ui/controllers/templates/api_keys_controller.rb.tt +20 -0
  21. data/lib/generators/rails_api_keys_ui/install/install_generator.rb +52 -0
  22. data/lib/generators/rails_api_keys_ui/install/templates/initializer.rb.tt +10 -0
  23. data/lib/generators/rails_api_keys_ui/install/templates/javascript/clipboard_controller.js +19 -0
  24. data/lib/generators/rails_api_keys_ui/install/templates/javascript/modal_controller.js +43 -0
  25. data/lib/generators/rails_api_keys_ui/locales/locales_generator.rb +17 -0
  26. data/lib/generators/rails_api_keys_ui/views/views_generator.rb +17 -0
  27. data/lib/rails_api_keys/ui/configuration.rb +24 -0
  28. data/lib/rails_api_keys/ui/engine.rb +39 -0
  29. data/lib/rails_api_keys/ui/mount_registry.rb +28 -0
  30. data/lib/rails_api_keys/ui/route_mapper.rb +55 -0
  31. data/lib/rails_api_keys/ui/version.rb +7 -0
  32. data/lib/rails_api_keys/ui.rb +39 -0
  33. data/lib/rails_api_keys_ui.rb +3 -0
  34. metadata +127 -0
@@ -0,0 +1,49 @@
1
+ <div data-modal-target="modal"
2
+ data-action="keydown@window->modal#closeOnEscape"
3
+ class="hidden fixed inset-0 z-50 overflow-y-auto">
4
+ <div class="flex items-center justify-center min-h-screen px-4">
5
+ <div data-modal-target="backdrop"
6
+ data-action="click->modal#closeOnBackdropClick"
7
+ class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
8
+ <div data-modal-target="panel"
9
+ class="relative bg-white rounded-lg shadow-xl max-w-md w-full p-6 z-10">
10
+ <div class="flex justify-between items-center mb-4">
11
+ <h3 class="text-lg font-medium text-gray-900"><%= title %></h3>
12
+ <button type="button"
13
+ data-action="click->modal#close"
14
+ class="text-gray-400 hover:text-gray-500">
15
+ ×
16
+ </button>
17
+ </div>
18
+ <%= form_with url: create_path, scope: :api_key, class: "space-y-4" do |f| %>
19
+ <div>
20
+ <%= f.label :name, t("rails_api_keys_ui.api_keys.form.name"), class: "block text-sm font-medium text-gray-700 mb-2" %>
21
+ <%= f.text_field :name, required: true, class: "form-input w-full border-gray-300 rounded-md shadow-sm", placeholder: t("rails_api_keys_ui.api_keys.form.name_placeholder") %>
22
+ </div>
23
+ <div>
24
+ <%= f.label :permission, t("rails_api_keys_ui.api_keys.form.permission"), class: "block text-sm font-medium text-gray-700 mb-2" %>
25
+ <%= f.select :permission,
26
+ options_for_select(
27
+ [
28
+ [ t("rails_api_keys_ui.api_keys.form.permission_read"), "read" ],
29
+ [ t("rails_api_keys_ui.api_keys.form.permission_read_write"), "read_write" ]
30
+ ],
31
+ "read"
32
+ ),
33
+ {},
34
+ required: true,
35
+ class: "form-input w-full border-gray-300 rounded-md shadow-sm" %>
36
+ </div>
37
+ <div class="flex justify-end space-x-3 pt-4">
38
+ <button type="button"
39
+ data-action="click->modal#close"
40
+ class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50">
41
+ <%= t("rails_api_keys_ui.api_keys.cancel") %>
42
+ </button>
43
+ <%= f.submit t("rails_api_keys_ui.api_keys.form.submit"),
44
+ class: "inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 cursor-pointer" %>
45
+ </div>
46
+ <% end %>
47
+ </div>
48
+ </div>
49
+ </div>
@@ -0,0 +1,28 @@
1
+ <% if api_keys.any? %>
2
+ <div class="space-y-3">
3
+ <% api_keys.each do |api_key| %>
4
+ <div class="flex flex-col gap-3 p-4 border border-gray-200 rounded-lg sm:flex-row sm:items-center sm:justify-between">
5
+ <div>
6
+ <p class="text-sm font-medium text-gray-900"><%= api_key.name %></p>
7
+ <p class="mt-1 text-sm text-gray-500 font-mono"><%= api_key.token_display_prefix %>…</p>
8
+ <p class="mt-1 text-xs text-gray-400">
9
+ <%= t("rails_api_keys_ui.api_keys.permission_#{api_key.permission}") %>
10
+ ·
11
+ <% if api_key.last_used_at.present? %>
12
+ <%= t("rails_api_keys_ui.api_keys.last_used", time: l(api_key.last_used_at, format: :short)) %>
13
+ <% else %>
14
+ <%= t("rails_api_keys_ui.api_keys.never_used") %>
15
+ <% end %>
16
+ </p>
17
+ </div>
18
+ <%= button_to t("rails_api_keys_ui.api_keys.revoke"),
19
+ destroy_path.respond_to?(:call) ? destroy_path.call(api_key) : destroy_path,
20
+ method: :delete,
21
+ class: "inline-flex items-center px-3 py-2 border border-red-300 rounded-md shadow-sm text-sm font-medium text-red-700 bg-white hover:bg-red-50",
22
+ data: { turbo_confirm: t("rails_api_keys_ui.api_keys.revoke_confirm") } %>
23
+ </div>
24
+ <% end %>
25
+ </div>
26
+ <% else %>
27
+ <p class="text-sm text-gray-500"><%= t("rails_api_keys_ui.api_keys.empty") %></p>
28
+ <% end %>
@@ -0,0 +1,27 @@
1
+ <%= rails_api_keys_ui_stylesheet %>
2
+ <div class="rails-api-keys-ui">
3
+ <div class="bg-white shadow rounded-lg p-6">
4
+ <div class="mb-6 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
5
+ <div>
6
+ <h2 class="text-lg font-medium text-gray-900"><%= t("rails_api_keys_ui.api_keys.title") %></h2>
7
+ <p class="mt-1 text-sm text-gray-500"><%= t("rails_api_keys_ui.api_keys.description") %></p>
8
+ </div>
9
+ <div data-controller="modal">
10
+ <button type="button"
11
+ data-action="click->modal#open"
12
+ class="inline-flex items-center px-3 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700">
13
+ <%= t("rails_api_keys_ui.api_keys.new") %>
14
+ </button>
15
+ <%= render "rails_api_keys/ui/api_keys/form_modal", create_path: create_path, title: t("rails_api_keys_ui.api_keys.new") %>
16
+ </div>
17
+ </div>
18
+
19
+ <%= render "rails_api_keys/ui/api_keys/list", api_keys: api_keys, destroy_path: destroy_path %>
20
+ </div>
21
+
22
+ <% if raw_token.present? %>
23
+ <div data-controller="modal" data-modal-open-on-connect-value="true">
24
+ <%= render "rails_api_keys/ui/api_keys/reveal_modal", raw_token: raw_token %>
25
+ </div>
26
+ <% end %>
27
+ </div>
@@ -0,0 +1,48 @@
1
+ <div data-modal-target="modal"
2
+ data-action="keydown@window->modal#closeOnEscape"
3
+ class="fixed inset-0 z-50 overflow-y-auto">
4
+ <div class="flex items-center justify-center min-h-screen px-4">
5
+ <div data-modal-target="backdrop"
6
+ data-action="click->modal#closeOnBackdropClick"
7
+ class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
8
+ <div data-modal-target="panel"
9
+ class="relative bg-white rounded-lg shadow-xl max-w-md w-full p-6 z-10">
10
+ <div class="flex justify-between items-center mb-4">
11
+ <h3 class="text-lg font-medium text-gray-900"><%= t("rails_api_keys_ui.api_keys.reveal.title") %></h3>
12
+ <button type="button"
13
+ data-action="click->modal#close"
14
+ class="text-gray-400 hover:text-gray-500">
15
+ ×
16
+ </button>
17
+ </div>
18
+ <p class="text-sm text-amber-700 bg-amber-50 border border-amber-200 rounded-md p-3 mb-4">
19
+ <%= t("rails_api_keys_ui.api_keys.reveal.warning") %>
20
+ </p>
21
+ <div data-controller="clipboard" class="space-y-3">
22
+ <label class="block text-sm font-medium text-gray-700"><%= t("rails_api_keys_ui.api_keys.reveal.token") %></label>
23
+ <div class="flex gap-2">
24
+ <input type="text"
25
+ readonly
26
+ data-clipboard-target="source"
27
+ value="<%= raw_token %>"
28
+ class="form-input font-mono text-sm flex-1 border-gray-300 rounded-md shadow-sm">
29
+ <button type="button"
30
+ data-action="click->clipboard#copy"
31
+ class="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50">
32
+ <%= t("rails_api_keys_ui.api_keys.reveal.copy") %>
33
+ </button>
34
+ </div>
35
+ <p data-clipboard-target="feedback" class="hidden text-sm text-primary-600">
36
+ <%= t("rails_api_keys_ui.api_keys.reveal.copied") %>
37
+ </p>
38
+ </div>
39
+ <div class="flex justify-end pt-6">
40
+ <button type="button"
41
+ data-action="click->modal#close"
42
+ class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700">
43
+ <%= t("rails_api_keys_ui.api_keys.reveal.done") %>
44
+ </button>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </div>
@@ -0,0 +1,5 @@
1
+ <%= rails_api_keys_ui_panel(
2
+ api_keys: @api_keys,
3
+ create_path: url_for(action: :create),
4
+ destroy_path: ->(api_key) { url_for(action: :destroy, id: api_key) }
5
+ ) %>
@@ -0,0 +1,42 @@
1
+ en:
2
+ rails_api_keys_ui:
3
+ api_keys:
4
+ title: "API keys"
5
+ description: "Create personal API keys for integrations."
6
+ new: "Create API key"
7
+ empty: "No API keys yet."
8
+ revoke: "Revoke"
9
+ revoke_confirm: "Revoke this API key? Integrations using it will stop working."
10
+ never_used: "Never used"
11
+ last_used: "Last used %{time}"
12
+ permission_read: "Read"
13
+ permission_read_write: "Read & write"
14
+ cancel: "Cancel"
15
+ form:
16
+ name: "Name"
17
+ name_placeholder: "e.g. Zapier production"
18
+ permission: "Permission"
19
+ permission_read: "Read"
20
+ permission_read_write: "Read & write"
21
+ submit: "Create API key"
22
+ reveal:
23
+ title: "Your new API key"
24
+ warning: "Copy this key now. You will not be able to see it again."
25
+ token: "API key"
26
+ copy: "Copy"
27
+ copied: "Copied to clipboard"
28
+ done: "Done"
29
+ # Lazy lookup scopes for engine controller (`t(".created")` / `t(".revoked")`)
30
+ rails_api_keys:
31
+ ui:
32
+ api_keys:
33
+ create:
34
+ created: "API key created. Copy it now — it won’t be shown again."
35
+ destroy:
36
+ revoked: "API key revoked."
37
+ # Lazy lookup when host subclasses as ApiKeysController
38
+ api_keys:
39
+ create:
40
+ created: "API key created. Copy it now — it won’t be shown again."
41
+ destroy:
42
+ revoked: "API key revoked."
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ RailsApiKeys::UI::Engine.routes.draw do
4
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RailsApiKeysUi
6
+ module Generators
7
+ class ControllersGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Copy rails_api_keys-ui controller into the host app"
11
+
12
+ def copy_controller
13
+ template "api_keys_controller.rb.tt", "app/controllers/api_keys_controller.rb"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApiKeysController < RailsApiKeys::UI::ApiKeysController
4
+ # Customize authorization or owner resolution here.
5
+ #
6
+ # Example (nested under employees):
7
+ #
8
+ # before_action :set_employee
9
+ # before_action :authorize_employee_api_keys!
10
+ #
11
+ # private
12
+ #
13
+ # def set_employee
14
+ # @employee = Employee.find(params[:employee_id])
15
+ # end
16
+ #
17
+ # def authorize_employee_api_keys!
18
+ # authorize @employee, :show_api_keys?
19
+ # end
20
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RailsApiKeysUi
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Install rails_api_keys-ui (initializer + optional Stimulus controllers)"
11
+
12
+ def copy_initializer
13
+ template "initializer.rb.tt", "config/initializers/rails_api_keys_ui.rb"
14
+ end
15
+
16
+ def copy_stimulus_controllers
17
+ return unless yes?("Copy example Stimulus modal/clipboard controllers into the host app?")
18
+
19
+ copy_file "javascript/modal_controller.js", "app/javascript/controllers/modal_controller.js"
20
+ copy_file "javascript/clipboard_controller.js", "app/javascript/controllers/clipboard_controller.js"
21
+ end
22
+
23
+ def show_readme
24
+ say <<~MSG
25
+
26
+ rails_api_keys-ui installed.
27
+
28
+ 1. Draw routes, for example:
29
+
30
+ resources :employees do
31
+ rails_api_keys_ui owner: :current_user, controller: "api_keys"
32
+ end
33
+
34
+ rails_api_keys_ui owner: :company, path: "company/api_keys"
35
+
36
+ 2. Views expect Stimulus controllers registered as `modal` and `clipboard`
37
+ (open/close + openOnConnect; copy from source target). Skip the copy
38
+ step above if your app already has compatible controllers.
39
+
40
+ 3. The panel inlines a scoped stylesheet by default. Override brand colors
41
+ with --rak-ui-primary-600 / --rak-ui-primary-700 on .rails-api-keys-ui,
42
+ or set config.include_stylesheet = false if you style it yourself.
43
+
44
+ 4. Optional: rails generate rails_api_keys_ui:controllers
45
+ rails generate rails_api_keys_ui:views
46
+ rails generate rails_api_keys_ui:locales
47
+
48
+ MSG
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RailsApiKeys::UI.configure do |config|
4
+ # # config.parent_controller = "ApplicationController"
5
+ # # config.current_owner = ->(controller) { controller.current_user }
6
+ # # config.policy_class = "RailsApiKeys::UI::ApiKeyPolicy"
7
+ # # config.raw_token_flash_key = :rails_api_keys_raw_token
8
+ # # config.include_stylesheet = true # false if the host already styles the panel
9
+ # # config.authorize = ->(controller, action, record) { ... }
10
+ # end
@@ -0,0 +1,19 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["source", "feedback"]
5
+
6
+ async copy() {
7
+ const value = this.sourceTarget.value
8
+ try {
9
+ await navigator.clipboard.writeText(value)
10
+ } catch (_error) {
11
+ this.sourceTarget.select()
12
+ document.execCommand("copy")
13
+ }
14
+
15
+ if (this.hasFeedbackTarget) {
16
+ this.feedbackTarget.classList.remove("hidden")
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,43 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["modal", "backdrop", "panel"]
5
+ static values = { openOnConnect: Boolean }
6
+
7
+ connect() {
8
+ if (this.openOnConnectValue) {
9
+ this.open()
10
+ return
11
+ }
12
+
13
+ if (this.hasModalTarget) {
14
+ this.modalTarget.classList.add("hidden")
15
+ }
16
+ }
17
+
18
+ open() {
19
+ if (this.hasModalTarget) {
20
+ this.modalTarget.classList.remove("hidden")
21
+ document.body.classList.add("overflow-hidden")
22
+ }
23
+ }
24
+
25
+ close() {
26
+ if (this.hasModalTarget) {
27
+ this.modalTarget.classList.add("hidden")
28
+ document.body.classList.remove("overflow-hidden")
29
+ }
30
+ }
31
+
32
+ closeOnBackdropClick(event) {
33
+ if (event.target === this.backdropTarget) {
34
+ this.close()
35
+ }
36
+ }
37
+
38
+ closeOnEscape(event) {
39
+ if (event.key === "Escape") {
40
+ this.close()
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RailsApiKeysUi
6
+ module Generators
7
+ class LocalesGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("../../../../../config/locales", __dir__)
9
+
10
+ desc "Copy rails_api_keys-ui locale files into the host app"
11
+
12
+ def copy_locales
13
+ copy_file "en.yml", "config/locales/rails_api_keys_ui.en.yml"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RailsApiKeysUi
6
+ module Generators
7
+ class ViewsGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("../../../../../app/views/rails_api_keys/ui", __dir__)
9
+
10
+ desc "Copy rails_api_keys-ui views into the host app for editing"
11
+
12
+ def copy_views
13
+ directory "api_keys", "app/views/rails_api_keys/ui/api_keys"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsApiKeys
4
+ module UI
5
+ # Host overrides. Set via {RailsApiKeys::UI.configure}.
6
+ class Configuration
7
+ attr_accessor :parent_controller,
8
+ :current_owner,
9
+ :authorize,
10
+ :policy_class,
11
+ :raw_token_flash_key,
12
+ :include_stylesheet
13
+
14
+ def initialize
15
+ @parent_controller = "ApplicationController"
16
+ @current_owner = ->(controller) { controller.current_user }
17
+ @authorize = nil
18
+ @policy_class = "RailsApiKeys::UI::ApiKeyPolicy"
19
+ @raw_token_flash_key = :rails_api_keys_raw_token
20
+ @include_stylesheet = true
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_api_keys/ui/route_mapper"
4
+
5
+ module RailsApiKeys
6
+ module UI
7
+ class Engine < ::Rails::Engine
8
+ isolate_namespace RailsApiKeys::UI
9
+
10
+ initializer "rails_api_keys_ui.inflections", before: :set_autoload_paths do
11
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
12
+ inflect.acronym "UI"
13
+ end
14
+ end
15
+
16
+ initializer "rails_api_keys_ui.routes", before: :add_routing_paths do
17
+ ActionDispatch::Routing::Mapper.include(RailsApiKeys::UI::RouteMapper)
18
+ end
19
+
20
+ initializer "rails_api_keys_ui.helpers" do
21
+ ActiveSupport.on_load(:action_controller_base) do
22
+ helper_path = RailsApiKeys::UI::Engine.root.join("app/helpers/rails_api_keys/ui/api_keys_helper.rb")
23
+ require helper_path.to_s unless defined?(RailsApiKeys::UI::ApiKeysHelper)
24
+ helper RailsApiKeys::UI::ApiKeysHelper
25
+ end
26
+ end
27
+
28
+ initializer "rails_api_keys_ui.assets" do |app|
29
+ next unless app.config.respond_to?(:assets)
30
+
31
+ app.config.assets.paths << root.join("app/javascript")
32
+ app.config.assets.paths << root.join("app/assets/stylesheets")
33
+ if app.config.assets.respond_to?(:precompile)
34
+ app.config.assets.precompile << "rails_api_keys/ui.css"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsApiKeys
4
+ module UI
5
+ # Stores per-draw-helper options (owner resolution, authorize, etc.).
6
+ # Route defaults only carry the mount name; procs live here.
7
+ class MountRegistry
8
+ def initialize
9
+ @mounts = {}
10
+ end
11
+
12
+ def register(name, options)
13
+ @mounts[name.to_s] = options.dup
14
+ end
15
+
16
+ def fetch(name)
17
+ @mounts.fetch(name.to_s) do
18
+ raise KeyError, "Unknown rails_api_keys_ui mount #{name.inspect}. " \
19
+ "Did routes reload without re-registering?"
20
+ end
21
+ end
22
+
23
+ def clear!
24
+ @mounts.clear
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsApiKeys
4
+ module UI
5
+ # Adds +rails_api_keys_ui+ to the host routes DSL.
6
+ module RouteMapper
7
+ # Draws +index+/+create+/+destroy+ API key routes for one owner mount.
8
+ #
9
+ # @param options [Hash]
10
+ # @option options [Symbol, String, Proc] :owner key owner resolution
11
+ # (:current_user, :company, or callable)
12
+ # @option options [Proc] :authorize optional per-mount authorize override
13
+ # @option options [String] :controller host or engine controller
14
+ # @option options [String] :path URL path segment (default "api_keys")
15
+ # @option options [String, Symbol] :name mount registry key
16
+ def rails_api_keys_ui(options = {})
17
+ opts = options.dup
18
+ mount_name = (opts.delete(:name) || default_rails_api_keys_ui_mount_name).to_s
19
+ owner = opts.key?(:owner) ? opts.delete(:owner) : :current_user
20
+ authorize = opts.delete(:authorize)
21
+ controller = opts.delete(:controller) || "rails_api_keys/ui/api_keys"
22
+ path = opts.delete(:path) || "api_keys"
23
+ as = opts.delete(:as)
24
+
25
+ RailsApiKeys::UI.mounts.register(
26
+ mount_name,
27
+ owner: owner,
28
+ authorize: authorize
29
+ )
30
+
31
+ resource_options = {
32
+ only: %i[index create destroy],
33
+ controller: controller,
34
+ path: path,
35
+ defaults: { rails_api_keys_ui_mount: mount_name }
36
+ }
37
+ resource_options[:as] = as if as
38
+
39
+ resources :api_keys, **resource_options, **opts
40
+ end
41
+
42
+ private
43
+
44
+ def default_rails_api_keys_ui_mount_name
45
+ scope = @scope
46
+ parts = []
47
+ parts << scope[:module] if scope[:module]
48
+ parts << scope[:as] if scope[:as]
49
+ parts << scope[:path] if scope[:path].present? && scope[:path] != "/"
50
+ parts << "api_keys"
51
+ parts.compact.join("_").gsub(%r{[/\-]}, "_").gsub(/__+/, "_")
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsApiKeys
4
+ module UI
5
+ VERSION = "0.2.0"
6
+ end
7
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_api_keys"
4
+ require "rails_api_keys/ui/version"
5
+ require "rails_api_keys/ui/configuration"
6
+ require "rails_api_keys/ui/mount_registry"
7
+
8
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
9
+ inflect.acronym "UI"
10
+ end
11
+
12
+ require "rails_api_keys/ui/engine"
13
+
14
+ module RailsApiKeys
15
+ # HTML/JSON management UI for {RailsApiKeys::ApiKey}.
16
+ module UI
17
+ class << self
18
+ def configuration
19
+ @configuration ||= Configuration.new
20
+ end
21
+
22
+ def configure
23
+ yield configuration
24
+ end
25
+
26
+ def reset_configuration!
27
+ @configuration = Configuration.new
28
+ end
29
+
30
+ def mounts
31
+ @mounts ||= MountRegistry.new
32
+ end
33
+
34
+ def reset_mounts!
35
+ mounts.clear!
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_api_keys/ui"