Location soft-delete: archiving replaces destructive deletion (#134) #169
No reviewers
Labels
No labels
area:ai
area:ci-cd
area:notifications
area:observability
area:public-pages
backlog
bug
duplicate
enhancement
help wanted
invalid
question
type:decision
type:feature
type:infra
type:maintenance
type:security
v1.0.1
v1.1.0
v1.2.0
v1.3.0
v2.0.0
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/WeatherBot!169
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/location-soft-delete"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #134. Decision and rationale recorded on the issue: soft-delete over detach-history —
owner_idandlocation_memberssurvive, so the three-tier visibility model keeps working unchanged for historical events (the part the issue flagged as easiest to get wrong under detach).Behavior
DELETE /api/locations/{id}now archives: setsdeleted_at, forcesenabled/public_enabledoff, revokes live public tokens, keeps every history row. Idempotent (double-archive is a no-op 204).?purge=true(admin-only, API-only) keeps the old hard delete + cascade for genuine full removal.GET /api/locations/{id}/delete-previewreturns retained-history counts; the locations page delete button now shows them in the confirm dialog ("history stays available — N alerts, N events, N snapshots").replace=truearchives instead of destroying; re-importing an archived location revives it (the only revival path — see below).0035adds the column.The audit
Every location-query site in the codebase got an explicit disposition (full table in the implementation commit): active surfaces are covered either by existing
enabled == Truefilters (which archive forces off) or newdeleted_atguards; per-location interactive/fetching endpoints 404 on archived; the correlation full rebuild and the AI-summary admin page deliberately include archived locations (history must keep deriving/rendering); plain metadataGET /api/locations/{id}stays readable so the Explorer can resolve an archived location's name/coords.Review pass (three fixes over the initial implementation)
PATCHand snooze/unsnooze now 404 on archived locations. An edit settingenabled=Trueon an archived row would create a zombie — polled by background jobs (which filter onenabled) while hidden from every list (which filter ondeleted_at). Revival stays deliberate-only via config import./p/{token}links (mirrors the existing PATCH public→private transition) instead of leaving them serving until retention expiry.'as', which the HTML parser decodes back into the JS source — any location name containing an apostrophe ("Aleesha's Home") broke the handler. Now passed via data attributes.Verification
@pytest.mark.postgrestest verifies purge's FK cascade under real enforcement (SQLite doesn't enforceON DELETE CASCADE); CI's Postgres tier runs it plus migration 0035.ruffclean;alembic heads→0035sole head.🤖 Generated with Claude Code
DELETE /api/locations/{id} now archives (sets deleted_at, forces enabled/public_enabled False) instead of hard-deleting: Location.sent_alerts cascades all,delete-orphan and the history tables (weather_events, nws_alert_snapshots, ...) cascade on location_id, so a hard delete silently destroyed the whole history the v2.0.0 Explorer exists to show. A privacy-style hard delete is still available via admin-only purge=true. Active-surface queries (list, dashboard, scheduler polling) exclude soft-deleted locations; history reads (alert history, event-correlation rebuild) deliberately do not. Config export/import now excludes/revives soft-deleted locations and replace=true soft-deletes rather than hard-deletes absent locations. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>- PATCH and snooze/unsnooze now 404 on an archived location: an edit setting enabled=True would create a zombie — polled by background jobs (which filter on enabled) while hidden from every list (which filter on deleted_at). Revival stays deliberate-only, via config import. - Soft-delete of a public location revokes its live /p/{token} links (mirrors the PATCH public->private transition) instead of leaving them serving until retention expiry. - The delete button passed the location name through a single-quoted JS string; Jinja renders an apostrophe as ' which the HTML parser decodes back into the JS source — any name containing one broke the handler. Now passed via data attributes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>