backend(boards): Collection.is_archived -> is_deleted + deleted_at (+ schema self-heal rename) #1667

Closed
opened 2026-10-15 11:40:25 +00:00 by shridhar.sabat · 4 comments
shridhar.sabat commented 2026-10-15 11:40:25 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Unify boards onto the soft-delete-to-Trash model (garments/fabrics/techpacks already use is_deleted + deleted_at).

Backend changes (anchors on origin/dev):

  • models/collection.py:28: replace is_archived with is_deleted = Column(Boolean, default=False, nullable=False, server_default FALSE) + deleted_at = Column(DateTime, nullable=True).
  • Schema self-heal: add services/collection_schema.py ensure_collection_trash_schema(engine): if is_archived exists and is_deleted is missing -> RENAME COLUMN is_archived TO is_deleted (preserves trashed rows on tier DBs); else ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE; ADD COLUMN deleted_at TIMESTAMP if missing. Wire into the boot ensure-chain (same caller as garment_schema.ensure) AND prove it against an old-schema DB. DEFAULT FALSE is mandatory (NULL rows vanish from == False filters).
  • routers/collections.py: DELETE soft path sets is_deleted=True + deleted_at=func.now(); POST /bulk-restore + POST /{board_id}/restore clear both; GET ?include_archived -> ?include_deleted; _board_dict emits is_deleted + deleted_at (drop is_archived); keep the PATCH non-patchable NOTE (update wording); keep owner-or-Admin #1511 + manage-gated purge (row + pins + deck_uploads).
  • Filters: routers/board_shares.py:111,119; services/feed_digest.py:158,186; services/feed_service.py:84,587,602,608-610; services/deck_uploads_janitor.py comment.
  • Tests: update test_boards_delete_ownership.py, test_board_shares.py, test_board_comments.py, test_boards_discovery_filters.py, test_boards_manage_sees_all.py, test_feed_soft_delete.py, test_deck_uploads_janitor.py + coverage_deep hits. Ownership semantics (#1511) unchanged.

Frozen contract for the frontend issue: Board JSON carries is_deleted: bool + deleted_at: str|null; GET /api/collections?include_deleted=true; DELETE -> {ok, hard} unchanged; restores unchanged shapes.

decision:

  • Garment status='archived' lifecycle value is KEPT (out of scope, per user 1+2 ruling).
  • ArchiveMasthead component/lib + "deck archive" copy are KEPT (branding, not the feature).
  • Live-list permanent-Delete buttons are removed on the frontend issue; purge stays manage-gated + owner-or-Admin, reachable only from Trash.

Acceptance: py_compile; touched pytest suites green; self-heal proven on old-schema sqlite (rename preserves trashed rows, legacy rows backfill False); grep is_archived in backend/ is zero except the kept items above.

Unify boards onto the soft-delete-to-Trash model (garments/fabrics/techpacks already use is_deleted + deleted_at). Backend changes (anchors on origin/dev): - models/collection.py:28: replace is_archived with is_deleted = Column(Boolean, default=False, nullable=False, server_default FALSE) + deleted_at = Column(DateTime, nullable=True). - Schema self-heal: add services/collection_schema.py ensure_collection_trash_schema(engine): if is_archived exists and is_deleted is missing -> RENAME COLUMN is_archived TO is_deleted (preserves trashed rows on tier DBs); else ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE; ADD COLUMN deleted_at TIMESTAMP if missing. Wire into the boot ensure-chain (same caller as garment_schema.ensure) AND prove it against an old-schema DB. DEFAULT FALSE is mandatory (NULL rows vanish from == False filters). - routers/collections.py: DELETE soft path sets is_deleted=True + deleted_at=func.now(); POST /bulk-restore + POST /{board_id}/restore clear both; GET ?include_archived -> ?include_deleted; _board_dict emits is_deleted + deleted_at (drop is_archived); keep the PATCH non-patchable NOTE (update wording); keep owner-or-Admin #1511 + manage-gated purge (row + pins + deck_uploads). - Filters: routers/board_shares.py:111,119; services/feed_digest.py:158,186; services/feed_service.py:84,587,602,608-610; services/deck_uploads_janitor.py comment. - Tests: update test_boards_delete_ownership.py, test_board_shares.py, test_board_comments.py, test_boards_discovery_filters.py, test_boards_manage_sees_all.py, test_feed_soft_delete.py, test_deck_uploads_janitor.py + coverage_deep hits. Ownership semantics (#1511) unchanged. Frozen contract for the frontend issue: Board JSON carries is_deleted: bool + deleted_at: str|null; GET /api/collections?include_deleted=true; DELETE -> {ok, hard} unchanged; restores unchanged shapes. ### decision: - Garment status='archived' lifecycle value is KEPT (out of scope, per user 1+2 ruling). - ArchiveMasthead component/lib + "deck archive" copy are KEPT (branding, not the feature). - Live-list permanent-Delete buttons are removed on the frontend issue; purge stays manage-gated + owner-or-Admin, reachable only from Trash. Acceptance: py_compile; touched pytest suites green; self-heal proven on old-schema sqlite (rename preserves trashed rows, legacy rows backfill False); grep is_archived in backend/ is zero except the kept items above.
shridhar.sabat commented 2026-10-15 11:41:10 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Board stage -> In Progress — backend boards conversion dispatched

**Board stage -> In Progress** — backend boards conversion dispatched
shridhar.sabat commented 2026-10-15 11:44:56 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Board stage -> In Progress — backend boards conversion dispatched

**Board stage -> In Progress** — backend boards conversion dispatched
shridhar.sabat commented 2026-10-15 15:08:00 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Shipped and verified (2026-10-15)

PR #1672 merged to dev as 7fb7994; deploy run 3766 green (deploy-dev + smoke + release notes); checkout HEAD == merge SHA; backend container carries the new code; frontend image dev-7fb7994.

Backend boards evidence (live dev PG):

  • collections has is_deleted + deleted_at, is_archived gone (boot RENAME worked)
  • 113/124 boards binned — every trashed row preserved (before: 11 live visible, after: 124-113=11 live)
  • API round trip on a probe board: create 201 (is_deleted=false) -> soft DELETE 200 -> hidden from default list -> ?include_deleted=true lists it -> restore 200 {ok, restored:true} -> back in default list -> purge 200 -> 404. Probe cleaned up.
  • pytest in tier image: 148/148 across the 10 trash/board/gallery suites.

UI: card menu is now a single Move to Trash (Archive + live Delete removed); detail has one Move to Trash button.

Screenshots (live)

pre-boards-menu-desktop.png

pre-boards-menu-desktop.png

after-boards-menu-desktop.png

after-boards-menu-desktop.png

after-boarddetail-desktop.png

after-boarddetail-desktop.png

### Shipped and verified (2026-10-15) PR #1672 merged to dev as 7fb7994; deploy run 3766 green (deploy-dev + smoke + release notes); checkout HEAD == merge SHA; backend container carries the new code; frontend image dev-7fb7994. Backend boards evidence (live dev PG): - collections has is_deleted + deleted_at, is_archived gone (boot RENAME worked) - 113/124 boards binned — every trashed row preserved (before: 11 live visible, after: 124-113=11 live) - API round trip on a probe board: create 201 (is_deleted=false) -> soft DELETE 200 -> hidden from default list -> ?include_deleted=true lists it -> restore 200 {ok, restored:true} -> back in default list -> purge 200 -> 404. Probe cleaned up. - pytest in tier image: 148/148 across the 10 trash/board/gallery suites. UI: card menu is now a single Move to Trash (Archive + live Delete removed); detail has one Move to Trash button. ### Screenshots (live) **pre-boards-menu-desktop.png** ![pre-boards-menu-desktop.png](/attachments/d3d1916f-889f-4267-9b78-63278ad73ca6) **after-boards-menu-desktop.png** ![after-boards-menu-desktop.png](/attachments/96d98560-2b3f-4035-aa75-b54513cdc9f5) **after-boarddetail-desktop.png** ![after-boarddetail-desktop.png](/attachments/6f6c12de-725f-4234-8e1d-dc3695fb5e91)
shridhar.sabat commented 2026-10-15 15:08:22 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Board stage -> Done — shipped to dev, verified live

**Board stage -> Done** — shipped to dev, verified live
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sapl/sapl#1667