mirror of
https://github.com/we-promise/sure.git
synced 2026-07-12 21:05:20 +00:00
* feat(api): allow multiple active API keys per user Previously a user could hold only one active API key; creating a new one silently revoked the existing key, breaking any app using it. Allow multiple named active keys instead. - Drop the one-active-key-per-source validation; add name uniqueness among the user's active visible keys (revoked names are reusable, same name allowed across users). - Rewrite Settings::ApiKeysController as a RESTful collection (index/show/new/create/destroy); create no longer revokes existing keys, and key lookup is scoped to the current user's active keys. - resource :api_key -> resources :api_keys. - Add an API keys list view with per-key revoke and an empty state; rewrite the show page for a single key. - Update i18n (remove single-key copy, pluralise nav label) and tests. * refactor(api): address review on multiple API keys - Remove unreachable destroy branches (cannot_revoke is guarded by the .visible 404; revoke! raises rather than returning false) and document that .visible is the demo-key revocation guard. - Delete the orphaned created.html.erb / created.turbo_stream.erb templates (no action renders them) and their unused locale keys. - Extract shared partials (_scope_badges, _status_indicator, _key_meta, _key_reveal, _usage) to de-duplicate the index and show views; unify the active-status indicator on the standard dot. - Carry forward the @container / @lg:flex-row / min-w-0 responsive fixes from #2079 into the shared key-reveal partial. * test(api): cover newly-created API key confirmation render * refactor(api): harden demo-key guard and address review nits - Document the demo-key revocation guard on ApiKey's `visible` scope (the authoritative spot) and add a model test locking the invariant that `.visible` excludes the demo monitoring key. - _scope_badges: use an i18n lookup with a humanize fallback instead of bypassing translation for unknown scopes. - _key_reveal: drop the hard-coded `id` from the shared partial; the system test now locates the key via its data-clipboard-target. * refactor(api-keys): migrate hand-rolled badges to DS::Pill Replace raw span elements in scope badges and status indicator with DS::Pill to align with the design system migration convention. * fix(loans): opening anchor now uses current balance, not original principal When creating a loan manually, the opening anchor valuation was being set to `initial_balance` (the original loan principal) instead of `account.balance` (the current outstanding balance). After the sync job ran, `account.balance` was overwritten to match the anchor, making every manually-created loan show its original principal as the current balance. Fix by always using `account.balance` for the opening anchor in `create_and_sync`, and reading `Loan#original_balance` from the `loans.initial_balance` column directly (with a fallback to `first_valuation_amount` for provider-synced loans that may not have the column populated). * fix(api-keys): strip accidental loan changes; rescue revoke! failures The fix(loans) commit was accidentally committed into this branch. Remove the loan-related changes from account.rb, loan.rb, and both test files, restoring them to their pre-loan-commit state. Also fix the destroy action: revoke! uses update! internally which raises ActiveRecord::RecordInvalid on failure rather than returning false. Add rescue for RecordInvalid and RecordNotDestroyed so failures produce a flash alert instead of a 500. Re-adds the revoke_failed locale key that was dropped from settings.api_keys.destroy. --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
68 lines
2.8 KiB
Plaintext
68 lines
2.8 KiB
Plaintext
<%= render DS::Dialog.new(variant: "modal", width: "md") do |dialog| %>
|
|
<% dialog.with_body do %>
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between gap-2">
|
|
<h3 class="font-medium text-primary">
|
|
<% if @api_key_present %>
|
|
<%= t("reports.google_sheets_instructions.title_with_key") %>
|
|
<% else %>
|
|
<%= t("reports.google_sheets_instructions.title_no_key") %>
|
|
<% end %>
|
|
</h3>
|
|
<%= icon("x", as_button: true, data: { action: "DS--dialog#close" }, class: "text-subdued hover:text-primary") %>
|
|
</div>
|
|
|
|
<div class="text-sm text-secondary space-y-3 whitespace-pre-line">
|
|
<% if @api_key_present %>
|
|
<p><%= t("reports.google_sheets_instructions.ready") %></p>
|
|
<p><%= t("reports.google_sheets_instructions.steps") %></p>
|
|
<div class="bg-surface-inset p-3 rounded-lg font-mono text-xs break-all">
|
|
=IMPORTDATA("<%= @csv_url %>")
|
|
</div>
|
|
<p class="text-warning"><%= icon("alert-triangle", class: "w-4 h-4 inline") %> <%= t("reports.google_sheets_instructions.security_warning") %></p>
|
|
<% else %>
|
|
<p><%= t("reports.google_sheets_instructions.need_key") %></p>
|
|
<ol class="list-decimal list-inside space-y-2">
|
|
<li><%= t("reports.google_sheets_instructions.step1") %></li>
|
|
<li><%= t("reports.google_sheets_instructions.step2") %></li>
|
|
<li><%= t("reports.google_sheets_instructions.step3") %></li>
|
|
<li><%= t("reports.google_sheets_instructions.step4") %></li>
|
|
</ol>
|
|
<p><strong><%= t("reports.google_sheets_instructions.example") %>:</strong></p>
|
|
<div class="bg-surface-inset p-3 rounded-lg font-mono text-xs break-all">
|
|
<%= @csv_url %>&api_key=YOUR_API_KEY_HERE
|
|
</div>
|
|
<p><%= t("reports.google_sheets_instructions.then_use") %></p>
|
|
<% end %>
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
<% if @api_key_present %>
|
|
<%= render DS::Button.new(
|
|
text: t("reports.google_sheets_instructions.open_sheets"),
|
|
variant: "primary",
|
|
full_width: true,
|
|
href: "https://sheets.google.com/create",
|
|
target: "_blank",
|
|
data: { action: "click->DS--dialog#close" }
|
|
) %>
|
|
<% else %>
|
|
<%= render DS::Button.new(
|
|
text: t("reports.google_sheets_instructions.go_to_api_keys"),
|
|
variant: "primary",
|
|
full_width: true,
|
|
href: settings_api_keys_path,
|
|
frame: "_top"
|
|
) %>
|
|
<% end %>
|
|
<%= render DS::Button.new(
|
|
text: t("reports.google_sheets_instructions.close"),
|
|
variant: "outline",
|
|
full_width: true,
|
|
data: { action: "DS--dialog#close" }
|
|
) %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% end %>
|