Commit Graph

1664 Commits

Author SHA1 Message Date
Stanko Krtalić eceb6c9b43 Add self-service account deletion (#2246)
* Add self-service account deletion

* Disable access to cancelled accounts & implement Stripe interactions

* Add tests

* Fix failing tests

* Remove cancelled accounts from lists

* Fix incorrect redirect

* Use _path instead of _url for consistency

* Don't track how far the inicieration job got

We still want the step tracking so that the job can be interrupted. But, since the scope is idempotent, we don't need to track how far it got.

* Specify the exact time when the data will be deleted

* Fix crash due to unadvancable cursor

* Rename up_for_incineration to due_for_incineration

* Regenrate the schema

* Fix incorrect path check

* Migrate the SQLite schema

* Only show the cancel button on cancellable accounts

* Check that a subscirption method exists before calling it

* Ignore job failures due to missing records when an account gets deleted

* Skip sending notifications on cancelled accounts

* Use collbacks to integrate

* Add a blank line between queue_as and discard_on

* Break checks into methods

* Inline methods

* Rename Account::IncinerateJob

* Run migrations

* Migrate SQLite
2026-01-12 14:21:05 +01:00
Jorge Manrubia 3457c871e1 Revert "Add CJK (Chinese, Japanese, Korean) search support" 2026-01-08 21:07:14 +01:00
Jorge Manrubia db804b8450 Merge pull request #2299 from Maklu/fix-cjk-search
Add CJK (Chinese, Japanese, Korean) search support
2026-01-08 12:00:46 +01:00
Jorge Manrubia 6d05ab428e Remove engagements
We are not using this anymore!
2026-01-07 16:43:33 +01:00
Jorge Manrubia 6f076dfe3c Merge pull request #2245 from wbso-ai/extend-slack-message
Add link to Fizzy card in Slack message
2026-01-07 16:37:51 +01:00
Jorge Manrubia 8efb778f53 Merge pull request #2237 from dilberryhoundog/fix-closed-cards-from-not_now
Fix: destroy "not now" state when closing cards, enables "indexed_by=closed" expected behaviour
2026-01-07 16:15:14 +01:00
Jorge Manrubia 6e765a114c Merge pull request #2294 from basecamp/refresh-drafts
Make sure new card drafts are refreshed when reused
2026-01-07 15:49:46 +01:00
LU 366588e263 Add SQLite FTS5 support for CJK search
- Split each CJK character into individual tokens for FTS5 indexing
- Add stem_content callback to SQLite adapter to pre-process content
- Stem search queries before matching against FTS5 index
- Update stemmer tests to reflect character-splitting behavior

This enables CJK search on SQLite installations where the FTS5
tokenizer cannot natively segment CJK text.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 17:51:20 +09:00
LU 2a4fbecbda Add CJK (Chinese, Japanese, Korean) search support
The search functionality was silently dropping CJK characters because:

1. Query sanitization used `\w` which only matches ASCII word characters
2. Stemmer split by whitespace, which doesn't work for CJK languages
3. Highlighter used `\b` word boundaries that don't apply to CJK

This commit fixes all three issues:

- Query: Use `\p{L}\p{N}` (Unicode letters/numbers) instead of `\w`
- Stemmer: Preserve CJK characters as-is without stemming, since CJK
  languages don't have stemming rules like English
- Highlighter: Skip word boundary matching for CJK terms

Also extracts `CJK_PATTERN` to `Search` module to avoid duplication.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-06 17:22:42 +09:00
Jorge Manrubia ffb9847a4e Merge pull request #2287 from italomatos/refactor/use-ids-method-instead-of-pluck
Refactor: Replace pluck(:id) with ids method
2026-01-05 16:03:41 +01:00
Jorge Manrubia 85bfa7aa48 Rename method 2026-01-05 16:01:25 +01:00
Jorge Manrubia 5f38e8957e Reapply "Make sure new card drafts are refreshed when reused"
This reverts commit 045ddf78f3.
2026-01-05 15:58:29 +01:00
Jorge Manrubia 045ddf78f3 Revert "Make sure new card drafts are refreshed when reused"
This reverts commit 3008011175.
2026-01-05 15:58:16 +01:00
Jorge Manrubia 3008011175 Make sure new card drafts are refreshed when reused
To deal with old timestamps messing with card ordering and so.

https://app.fizzy.do/5986089/cards/3495
2026-01-05 15:57:48 +01:00
Italo Matos 4189261cd0 Refactor: Replace pluck(:id) with ids method
Use the more idiomatic ActiveRecord ids method instead of pluck(:id)
across controllers and models. The ids method is more readable and
explicitly conveys the intent to retrieve primary key values.

Changes:
- BoardsController#edit: Use @board.users.ids
- Board::Storage: Use cards.ids, Comment.where().ids, and ActionText::RichText.where().ids
- User::Accessor: Use account.boards.all_access.ids

This change improves code clarity while maintaining the same functionality.
2026-01-04 10:02:58 -03:00
Jeremy Daer 0eefd67b68 Block IPv4-compatible IPv6 addresses in SSRF protection (#2273)
The SSRF filter checked ipv4_mapped? but not ipv4_compat?, allowing
addresses like ::169.254.169.254 to bypass the link-local check and
reach cloud metadata endpoints.

Changes:
- Add ipv4_compat? check to block deprecated IPv4-compatible format
- Rename private_address? to blocked_address? (more accurate - method
  blocks more than just RFC 1918 private ranges)
- Add IPv6 test coverage for both mapped and compat formats

Both ipv4_mapped and ipv4_compat formats are blocked entirely as
defense-in-depth: DNS never returns these formats, so they only
appear in attack scenarios.

HackerOne: #3481701
2025-12-29 21:45:06 -08:00
Rosa Gutierrez f66a2dc1ba Consider user avatars always public
Avatars are purposely accessible without authentication
(5e3b5b6d7c) because they can be in public
collections. Trying to restrict this by checking whether they're in fact
present in some public collection is rather expensive, so let's keep
them public.
2025-12-26 19:33:26 +01:00
Rosa Gutierrez 196d685f8d Implement authorization for Active Storage endpoints
Consider blobs attached to any public records accessible to anyone with
the URL.
2025-12-25 21:22:36 +01:00
Jankees van Woezik cf82ead0e3 Add link to Fizzy to Slack message
This makes it nice to use with a Slack webhook enabled, see
also this discussion: https://github.com/basecamp/fizzy/discussions/2238
2025-12-24 11:16:14 +01:00
Dylan a005391724 Fix: allow destroying "not now" state when closing cards
- Update `Card::Closeable` to destroy associated "not now" state during close.
- Add new tests to verify closure behavior for different column types, including "not now".
2025-12-23 13:43:03 +08:00
Stanko K.R. 0df667f4fb Fix HTML injection in webhooks through card titles 2025-12-22 12:20:32 +01:00
Jeremy Daer bd6c1cf34f Storage: harden reconcile for concurrent writes and fix board transfer (#2096)
* 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
2025-12-19 13:07:32 -08:00
Jorge Manrubia 2cbbdfbd19 Merge pull request #2138 from italomatos/refactor/replace-reverse-merge-with-with-defaults
Refactor: Replace reverse_merge with with_defaults for improved readability
2025-12-19 11:24:46 +01:00
Rosa Gutierrez cb01966439 Remove unused Card.published_or_drafted_by scope
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.
2025-12-19 09:46:21 +01:00
Samuel Péchèr 32a0acea11 Merge pull request #2107 from basecamp/adjust-paragraph-spacing
Adjusting text content spacing between paragraphs
2025-12-19 07:59:25 +01:00
Adrien Maston 972f86b603 Merge pull request #2177 from basecamp/mobile-app/scoped-styles
Mobile app / Scoped styles
2025-12-18 17:57:46 +01:00
Stanko Krtalić 42185a8d6d Merge pull request #2153 from basecamp/limit-the-number-of-asignees-on-a-card
Limit the number of asignees on a card
2025-12-18 12:08:45 +01:00
Samuel Péchèr d250c256c6 Add paragraph space to seed Card #8 2025-12-18 09:27:56 +00:00
Adrien Maston 5b7aa67bb3 Add platform attribute 2025-12-17 08:56:08 +01:00
Jorge Manrubia 0278fe6ae4 Revert "Mobile app / Scoped stylesheets" (#1698)
This reverts commit 39c1906e67.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 17:20:18 +01:00
Mike Dalessio 2b388dd241 Merge pull request #2164 from basecamp/flavorjones/user-validate-name
Validate User name presence and handle blank names gracefully
2025-12-16 10:49:13 -05:00
Jorge Manrubia ee80b87c8c Add billing system with Stripe
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Jason Zimdars <jz@37signals.com>
2025-12-16 16:44:20 +01:00
Mike Dalessio b75d2ae6d8 Validate User name presence and handle blank names gracefully
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.
2025-12-16 10:44:10 -05:00
Adrien Maston 04332bc526 Merge branch 'main' into mobile-app/scoped-stylesheets
* main:
  Use decimals for ordered lists
  Update cached fragments
  Revert "Merge pull request #1865 from basecamp/public-avatar-caching"
  Show only public cards on public boards
  Swap order of avatar links
  Limit length of full name during signup
  Make layout bulletproof
  Serve own avatar from its own endpoint
2025-12-16 15:30:31 +01:00
Stanko K.R. b0f9960b8a Raise the limit to 100 2025-12-16 13:36:40 +01:00
Stanko K.R. 15daa2bf05 Limit asignees to 10 per card 2025-12-15 16:40:52 +01:00
Kevin McConnell 87c6557747 Limit length of full name during signup
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.
2025-12-15 09:56:35 +00:00
Adrien Maston 1f346085b9 Merge branch 'main' into mobile-app/scoped-stylesheets
* main: (119 commits)
  Bust comment view cache
  Update lexxy to bring fixes from https://github.com/basecamp/lexxy/releases/tag/v0.1.24.beta
  Fix unexpected remove empty line from README
  Lightbox uses Stimulus target callbacks instead of data-action
  Add test coverage for the image lightbox
  Add tests for tenancy middleware and timezone cookie
  Refactor: Simplify TimeWindowParser using Rails convenience methods
  SMTP: support SMTPS on port 465 (#2132)
  Bump fizzy-saas to retain fewer docker images (#2134)
  Add test coverage for with_golden_first scope (#2130)
  Refactor: improve query scope composition with merge syntax (#2131)
  Validate avatar sizes
  Introduce Vips configuration
  Tailscale serve support (#2126)
  Update tests with final method naming: record! -> record
  CSP config to allow Minio in development
  Update fizzy-saas to get employee restriction in staging
  Drop staff restriction in beta and staging
  Unused
  Use new FIZZY_GH_TOKEN with limited access
  ...
2025-12-15 10:43:32 +01:00
Italo Matos cd4fbc011c Refactor: Replace reverse_merge with with_defaults for improved readability
Replace all occurrences of reverse_merge with with_defaults across the codebase.
The with_defaults method provides clearer intent and better readability when
setting default values for hash parameters.

Changes:
- app/helpers/columns_helper.rb: Update column_frame_tag method
- app/models/user/email_address_changeable.rb: Update generate_email_address_change_token method
- app/models/account.rb: Update create_with_owner method
- app/controllers/concerns/filter_scoped.rb: Update filter_params method

This refactoring maintains the same functionality while improving code clarity.
2025-12-14 14:22:06 -03:00
Jorge Manrubia 01a5d194ba Merge pull request #2135 from italomatos/refactor/improve-time-window-parser-readability
Refactor: Simplify TimeWindowParser using Rails convenience methods
2025-12-14 09:42:07 +01:00
Jorge Manrubia d7e5d4218f Merge pull request #2032 from tomycostantino/update-columns-on-actions
Fix: board columns actions are stale when moving a column moves
2025-12-14 09:28:57 +01:00
Italo Matos 5ade7e1a52 Refactor: Simplify TimeWindowParser using Rails convenience methods
- Replace `beginning_of_day..end_of_day` with `all_day`
- Replace `beginning_of_week..end_of_week` with `all_week`
- Replace `beginning_of_month..end_of_month` with `all_month`
- Replace `beginning_of_year..end_of_year` with `all_year`

These changes improve code readability by using idiomatic Rails
methods that accomplish the same thing in a more concise and
expressive way.
2025-12-13 16:31:48 -03:00
Mike Dalessio 3584df6816 Merge pull request #2133 from basecamp/flavorjones/handle-image-uploads-better
Improve avatar image handling
2025-12-13 13:25:54 -05:00
Ítalo Matos fbc586646f Refactor: improve query scope composition with merge syntax (#2131)
* Refactor: improve query scope composition with merge syntax

Replace manual WHERE clause concatenation with Rails' merge method
for more elegant and maintainable scope composition across Card,
Comment, and Filter models. This approach better follows Rails
conventions and improves code readability.

* Extend scope composition improvements to Card::Closeable

Apply the same nested hash syntax pattern to closures table references
in order and where clauses.

* Remove unnecessary outer braces from where clause

---------

Co-authored-by: Jeremy Daer <jeremy@37signals.com>
2025-12-13 10:10:13 -08:00
Mike Dalessio 139bf3cf81 Validate avatar sizes 2025-12-13 13:05:30 -05:00
Stanko K.R. c8a5d01771 Wrap join code redemption in a lock 2025-12-12 09:59:42 +01:00
Stanko K.R. a4d11e169f Replace custom code generator with Base32 2025-12-12 07:45:23 +01:00
Ítalo Matos 2e33262960 Refactor: Use Rails range syntax in ActivitySpike query (#2080)
Replace SQL string syntax with Rails range syntax for date filtering
in the ActivitySpike::Detector. This improves code readability and
follows Rails idioms.

Changed from:
  .where("created_at >= ?", recent_period.seconds.ago)

To:
  .where(created_at: recent_period.seconds.ago..)

This modernizes the codebase while maintaining the same functionality.
2025-12-11 20:40:31 -08:00
Ítalo Matos 0833c52aa0 Refactor: use idiomatic .last instead of .order(:desc).first (#2098)
Simplifies the last_event method in ActivitySpike::Detector by using
the more idiomatic Rails pattern .order(:created_at).last instead of
.order(created_at: :desc).first. Both generate the same SQL query but
.last is more readable and conventional in Rails codebases.
2025-12-11 20:35:38 -08:00
Jankees van Woezik 78fc80cf35 API: Allow updates to last_active_at (#2076)
* Allow Card#last_updated_at to be set

This is useful when doing an import from another system. I'm currently
working on a script to import our Github issues into Fizzy.

This is discussed in
https://github.com/basecamp/fizzy/pull/2056#discussion_r2609560246

* Add nil fallback and expand test coverage for last_active_at

Adds a safety fallback to Time.current if created_at is unexpectedly nil
during card creation.

Test coverage to verify:
* last_active_at defaults to created_at when not provided
* last_active_at can be updated via API on existing cards
* import workflow where last_active_at is restored after comments
* publishing doesn't overwrite explicit last_active_at values

---------

Co-authored-by: Jeremy Daer <jeremy@37signals.com>
2025-12-11 19:06:03 -08:00