* Storage: harden reconcile for concurrent writes and fix board transfer
Reconcile now uses two-cursor approach: captures cursor before and after
the storage scan, aborting if they differ (entries added during scan).
Job retries 3x with 1-minute waits and limits concurrency to 1 per owner.
Board transfer now correctly moves storage for card description embeds
and comment embeds, not just direct attachments. Uses batched queries
to handle cards with thousands of comments efficiently.
Also fixes N+1 queries in attachment grouping via lookup maps.
* Storage: fix per-attachment reconcile and enforce no blob reuse
The storage ledger tracks per-attachment (not per-blob) as a business
abstraction for quotas. This fixes reconcile to match that model and
adds enforcement to prevent blob reuse in tracked contexts.
* Reconcile now uses joins(:blob).sum() for per-attachment counting
* New validation prevents reusing blobs across tracked attachments
* Race-safe storage_total creation with create_or_find_by
* Job efficiency: skip find_by when object already available
* Backfill script checks per-attachment, not just per-blob
We had a call about this. In short, we could reuse access tokens but then the user would see access tokens for every mobile device they have without any indication as to what is going on. So, since this really is just logging in instead of an integration which seems to be the primary purpose of access tokens, we can just use our regular session cookie for authentication.
As the tests for it could lead to confusion where it seems drafted cards
are not accessible to someone with access to the board and with the
direct drafted card URL.
replacing normalize_base36. This should make it easier to convert
UUIDs between hex and base36 formats, useful when debugging production
logs that contain hex UUIDs.
For example, if in a log you see:
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE
`users`.`account_id` = x'019afab815897a4f920f3a24fab75400' AND
`users`.`id` = x'019afab815a2790688c58c7f8326e700' LIMIT 1
and you want to find that account or that user in the console, you can
now do:
id = ActiveRecord::Type::Uuid.hex_to_base36("019afab815897a4f920f3a24fab75400")
=> "03f6bilrvt3oghzhurll4pp8g"
Addresses an issue where User#familiar_name assumed `name` was always
present, potentially raising an exception during view rendering. Now
User validates name presence, and User#familiar_name handles blank
strings without error, in case any existing invalid records exist.
If we don't validate for length, then signups that overflow the database
columns will unnecessarily create and cancel a tenant. Adding a
validation means we can avoid this.
Add account data to fresh_when etag arrays so Rails serves fresh responses after account changes.
- Add @accounts to menus controller etag
- Add Current.account to boards controller etag
- Add ETag cache invalidation tests for both controllers
Remove data-action from the sanitizer allowlist to disallow injection
of potentially malicious Stimulus actions in user-provided
content. The lightbox controller now uses imageTarget callbacks to
handle clicks on image links.
Also add the file name as a caption in the light box, and fix the
caption color for dark mode visibility.
* Add test coverage for with_golden_first scope
Test verifies that the with_golden_first scope correctly orders
golden cards before non-golden cards in query results.
* Refactor golden test: use instance variables, add subordering coverage
Extract @golden and @non_golden fixtures into setup for reuse across
all tests. Simplify with_golden_first test to verify both primary
ordering (golden before non-golden) and subordering preservation.
---------
Co-authored-by: Jeremy Daer <jeremy@37signals.com>
And close the gap with JSON requests, which shouldn't be allowed if
Sec-Fetch-Site is 'cross-site' or 'none', only if it's empty as this
wouldn't be coming from a browser.
Adds `closed` field to card JSON response, allowing API consumers
to detect closed status without parsing the status enum or making
additional API calls.
This allows us to have different cache controls depending on whether
you're viewing your own avatar, or someone else's. Your own avatar will
always be fresh, while other folks' avatars can be pulled from the CDN.