mirror of
https://github.com/we-promise/sure.git
synced 2026-04-12 16:47:22 +00:00
* Add post-trial inactive family cleanup with data archival Families that expire their trial without subscribing now get cleaned up daily. Empty families (no accounts) are destroyed immediately after a 14-day grace period. Families with meaningful data (12+ transactions, some recent) get their data exported as NDJSON/ZIP to an ArchivedExport record before deletion, downloadable via a token-based URL for 90 days. - Add InactiveFamilyCleanerJob (scheduled daily at 4 AM, managed mode only) - Add ArchivedExport model with token-based downloads - Add inactive_trial_for_cleanup scope and requires_data_archive? to Family - Extend DataCleanerJob to purge expired archived exports - Add ArchivedExportsController for unauthenticated token downloads https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ * Fix Brakeman redirect warning in ArchivedExportsController Use rails_blob_path instead of redirecting directly to the ActiveStorage attachment, which avoids the allow_other_host: true open redirect. https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ * Update schema.rb with archived_exports table Add the archived_exports table definition to schema.rb to match the pending migration, unblocking CI tests. https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ * Fix broken CI tests for ArchivedExports and InactiveFamilyCleaner - ArchivedExportsController 404 test: use assert_response :not_found instead of assert_raises since Rails rescues RecordNotFound in integration tests and returns a 404 response. - InactiveFamilyCleanerJob test: remove assert_no_difference on Family.count since the inactive_trial fixture gets cleaned up by the job. The test intent is to verify the active family survives, which is checked by assert Family.exists?. https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ * Wrap ArchivedExport creation in a transaction Ensure the ArchivedExport record and its file attachment succeed atomically. If the attach fails, the transaction rolls back so no orphaned record is left without an export file. https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ * Store only a digest of the download token for ArchivedExport Replace plaintext download_token column with download_token_digest (SHA-256 hex). The raw token is generated via SecureRandom on create, exposed transiently via attr_reader for use in emails/logs, and only its digest is persisted. Lookup uses find_by_download_token! which digests the incoming token before querying. https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ * Remove raw download token from cleanup job logs Log a truncated digest prefix instead of the raw token, which is the sole credential for the unauthenticated download endpoint. https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ * Fix empty assert_no_difference block in cleaner job test Wrap the perform_now call with both assertions so the ArchivedExport.count check actually exercises the job. https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ --------- Co-authored-by: Claude <noreply@anthropic.com>
103 lines
2.4 KiB
YAML
103 lines
2.4 KiB
YAML
empty:
|
|
family: empty
|
|
first_name: User
|
|
last_name: One
|
|
email: user1@example.com
|
|
password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
|
|
onboarded_at: <%= 3.days.ago %>
|
|
role: admin
|
|
ai_enabled: true
|
|
show_sidebar: true
|
|
show_ai_sidebar: true
|
|
ui_layout: dashboard
|
|
|
|
sure_support_staff:
|
|
family: empty
|
|
first_name: Support
|
|
last_name: Admin
|
|
email: support@sure.am
|
|
password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
|
|
role: super_admin
|
|
onboarded_at: <%= 3.days.ago %>
|
|
ai_enabled: true
|
|
show_sidebar: true
|
|
show_ai_sidebar: true
|
|
ui_layout: dashboard
|
|
|
|
family_admin:
|
|
family: dylan_family
|
|
first_name: Bob
|
|
last_name: Dylan
|
|
email: bob@bobdylan.com
|
|
password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
|
|
role: admin
|
|
onboarded_at: <%= 3.days.ago %>
|
|
ai_enabled: true
|
|
show_sidebar: true
|
|
show_ai_sidebar: true
|
|
ui_layout: dashboard
|
|
|
|
family_member:
|
|
family: dylan_family
|
|
first_name: Jakob
|
|
last_name: Dylan
|
|
email: jakobdylan@yahoo.com
|
|
password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
|
|
onboarded_at: <%= 3.days.ago %>
|
|
role: member
|
|
ai_enabled: true
|
|
show_sidebar: true
|
|
show_ai_sidebar: true
|
|
ui_layout: dashboard
|
|
|
|
new_email:
|
|
family: empty
|
|
first_name: Test
|
|
last_name: User
|
|
email: user@example.com
|
|
unconfirmed_email: new@example.com
|
|
password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
|
|
onboarded_at: <%= Time.current %>
|
|
role: member
|
|
ai_enabled: true
|
|
show_sidebar: true
|
|
show_ai_sidebar: true
|
|
ui_layout: dashboard
|
|
|
|
intro_user:
|
|
family: empty
|
|
first_name: Intro
|
|
last_name: User
|
|
email: intro@example.com
|
|
password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
|
|
onboarded_at: <%= 1.day.ago %>
|
|
role: guest
|
|
ai_enabled: true
|
|
show_sidebar: false
|
|
show_ai_sidebar: false
|
|
ui_layout: intro
|
|
|
|
inactive_trial_user:
|
|
family: inactive_trial
|
|
first_name: Inactive
|
|
last_name: User
|
|
email: inactive@example.com
|
|
password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
|
|
role: admin
|
|
onboarded_at: <%= 90.days.ago %>
|
|
ai_enabled: true
|
|
show_sidebar: true
|
|
show_ai_sidebar: true
|
|
ui_layout: dashboard
|
|
|
|
# SSO-only user: created via JIT provisioning, no local password
|
|
sso_only:
|
|
family: empty
|
|
first_name: SSO
|
|
last_name: User
|
|
email: sso-user@example.com
|
|
password_digest: ~
|
|
role: admin
|
|
onboarded_at: <%= 1.day.ago %>
|
|
ai_enabled: true
|