feat(admin-feedback): per-row Archive action + Reply button discoverability #115

Merged
YousifShkara merged 1 commit from feat/feedback-admin-archive-and-reply-button into main 2026-06-11 07:39:08 +02:00
Owner

Closes BUNYIP-93. Stacks on BUNYIP-92.

Two complaints from the admin dashboard:

  1. The Archive TAB shipped in BUNYIP-85 with a Restore button, but no path INTO the archive. The only existing archive mechanism was the batch background job FeedbackRepository::archive_and_purge_closed (crates/bunyip-domain/src/repositories/feedback.rs:284), which moves status='closed' AND updated_at < NOW() - INTERVAL '90 days' rows into feedback_archive. An admin wanting to archive a row immediately had no path; the existing batch job is non-interactive.

  2. "There's no way to reply." The reply form has shipped since BUNYIP-85, but lives on the detail subpage; the row's clickable subject is the only way in and it wasn't obvious. The detail subpage at /admin/feedback/:id has the textarea + send button; the API sends an email to the submitter.

Changes:

  • crates/bunyip-domain/src/models/audit.rs: add AuditAction::FeedbackArchived (serialises to feedback_archived, listed as an admin action).
  • crates/bunyip-domain/src/repositories/feedback.rs: new archive_one(pool, id) method modelled on the existing batch SQL (INSERT INTO feedback_archive ... DELETE FROM feedback) but scoped to one id and wrapped in a transaction so a crash mid-way cannot duplicate or lose the row. Returns AppError::not_found when the id is not present. Attachments cascade-delete with the source row, matching the existing batch path; restore (BUNYIP-85) does not bring attachments back. Existing trade-off; not changing it here.
  • bunyip-api/src/handlers/feedback.rs: new archive_feedback handler at POST /v1/admin/feedback/{id}/archive. Writes a FeedbackArchived audit log row on success.
  • bunyip-api/src/handlers/mod.rs + bunyip-api/src/routes/admin.rs: re-export and route registration adjacent to mark-spam / unmark-spam.
  • bunyip-web/src/api/admin.rs: new archive_feedback BFF helper.
  • bunyip-web/src/handlers/admin.rs:
    • Row template: an Archive button next to the existing tab-aware action group on Active / Closed / Spam tabs (Archive tab itself has Restore from BUNYIP-85, no Archive button needed).
    • Row template: a "Reply" anchor styled as a primary button on Active and Closed rows. It is a link to /admin/feedback/:id, not a separate action - the reply form is the existing detail subpage. Hidden on Spam and Archive tabs (replying to spam is meaningless; archived rows are out of queue).
    • Detail subpage gains an Archive button next to the existing Mark Spam + Delete buttons.
    • New feedback_archive_action SSR handler at POST /admin/feedback/:id/archive, follows the same from hidden-field redirect pattern BUNYIP-92 introduced so the admin lands back on the originating tab with a ?toast_ok=Archived toast.
  • bunyip-web/src/main.rs: register the new route adjacent to mark-spam / unmark-spam / delete.

Naming: the existing feedback_archive GET handler (BUNYIP-85) lists archived rows. The new feedback_archive_action POST handler archives a single row. The two names differ so the router and the reader can tell them apart at a glance.

just check-container clean (modulo the pre-existing test_config_defaults parallel-env-var flake on main; 188 other tests pass; fmt + clippy + build clean).

#BUNYIP-93

Closes BUNYIP-93. Stacks on BUNYIP-92. Two complaints from the admin dashboard: 1. The Archive TAB shipped in BUNYIP-85 with a Restore button, but no path INTO the archive. The only existing archive mechanism was the batch background job `FeedbackRepository::archive_and_purge_closed` (`crates/bunyip-domain/src/repositories/feedback.rs:284`), which moves status='closed' AND updated_at < NOW() - INTERVAL '90 days' rows into `feedback_archive`. An admin wanting to archive a row immediately had no path; the existing batch job is non-interactive. 2. "There's no way to reply." The reply form has shipped since BUNYIP-85, but lives on the detail subpage; the row's clickable subject is the only way in and it wasn't obvious. The detail subpage at `/admin/feedback/:id` has the textarea + send button; the API sends an email to the submitter. Changes: - crates/bunyip-domain/src/models/audit.rs: add `AuditAction::FeedbackArchived` (serialises to `feedback_archived`, listed as an admin action). - crates/bunyip-domain/src/repositories/feedback.rs: new `archive_one(pool, id)` method modelled on the existing batch SQL (`INSERT INTO feedback_archive ... DELETE FROM feedback`) but scoped to one id and wrapped in a transaction so a crash mid-way cannot duplicate or lose the row. Returns `AppError::not_found` when the id is not present. Attachments cascade-delete with the source row, matching the existing batch path; restore (BUNYIP-85) does not bring attachments back. Existing trade-off; not changing it here. - bunyip-api/src/handlers/feedback.rs: new `archive_feedback` handler at `POST /v1/admin/feedback/{id}/archive`. Writes a `FeedbackArchived` audit log row on success. - bunyip-api/src/handlers/mod.rs + bunyip-api/src/routes/admin.rs: re-export and route registration adjacent to mark-spam / unmark-spam. - bunyip-web/src/api/admin.rs: new `archive_feedback` BFF helper. - bunyip-web/src/handlers/admin.rs: - Row template: an Archive button next to the existing tab-aware action group on Active / Closed / Spam tabs (Archive tab itself has Restore from BUNYIP-85, no Archive button needed). - Row template: a "Reply" anchor styled as a primary button on Active and Closed rows. It is a link to `/admin/feedback/:id`, not a separate action - the reply form is the existing detail subpage. Hidden on Spam and Archive tabs (replying to spam is meaningless; archived rows are out of queue). - Detail subpage gains an Archive button next to the existing Mark Spam + Delete buttons. - New `feedback_archive_action` SSR handler at `POST /admin/feedback/:id/archive`, follows the same `from` hidden-field redirect pattern BUNYIP-92 introduced so the admin lands back on the originating tab with a `?toast_ok=Archived` toast. - bunyip-web/src/main.rs: register the new route adjacent to mark-spam / unmark-spam / delete. Naming: the existing `feedback_archive` GET handler (BUNYIP-85) lists archived rows. The new `feedback_archive_action` POST handler archives a single row. The two names differ so the router and the reader can tell them apart at a glance. `just check-container` clean (modulo the pre-existing `test_config_defaults` parallel-env-var flake on main; 188 other tests pass; fmt + clippy + build clean). #BUNYIP-93
feat(admin-feedback): per-row Archive action + Reply button discoverability
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt / clippy / build / test (pull_request) Successful in 1m10s
f0717971b0
Closes BUNYIP-93. Stacks on BUNYIP-92.

Two complaints from the admin dashboard:

1. The Archive TAB shipped in BUNYIP-85 with a Restore button, but no path INTO the archive. The only existing archive mechanism was the batch background job `FeedbackRepository::archive_and_purge_closed` (`crates/bunyip-domain/src/repositories/feedback.rs:284`), which moves status='closed' AND updated_at < NOW() - INTERVAL '90 days' rows into `feedback_archive`. An admin wanting to archive a row immediately had no path; the existing batch job is non-interactive.

2. "There's no way to reply." The reply form has shipped since BUNYIP-85, but lives on the detail subpage; the row's clickable subject is the only way in and it wasn't obvious. The detail subpage at `/admin/feedback/:id` has the textarea + send button; the API sends an email to the submitter.

Changes:

- crates/bunyip-domain/src/models/audit.rs: add `AuditAction::FeedbackArchived` (serialises to `feedback_archived`, listed as an admin action).
- crates/bunyip-domain/src/repositories/feedback.rs: new `archive_one(pool, id)` method modelled on the existing batch SQL (`INSERT INTO feedback_archive ... DELETE FROM feedback`) but scoped to one id and wrapped in a transaction so a crash mid-way cannot duplicate or lose the row. Returns `AppError::not_found` when the id is not present. Attachments cascade-delete with the source row, matching the existing batch path; restore (BUNYIP-85) does not bring attachments back. Existing trade-off; not changing it here.
- bunyip-api/src/handlers/feedback.rs: new `archive_feedback` handler at `POST /v1/admin/feedback/{id}/archive`. Writes a `FeedbackArchived` audit log row on success.
- bunyip-api/src/handlers/mod.rs + bunyip-api/src/routes/admin.rs: re-export and route registration adjacent to mark-spam / unmark-spam.
- bunyip-web/src/api/admin.rs: new `archive_feedback` BFF helper.
- bunyip-web/src/handlers/admin.rs:
  - Row template: an Archive button next to the existing tab-aware action group on Active / Closed / Spam tabs (Archive tab itself has Restore from BUNYIP-85, no Archive button needed).
  - Row template: a "Reply" anchor styled as a primary button on Active and Closed rows. It is a link to `/admin/feedback/:id`, not a separate action - the reply form is the existing detail subpage. Hidden on Spam and Archive tabs (replying to spam is meaningless; archived rows are out of queue).
  - Detail subpage gains an Archive button next to the existing Mark Spam + Delete buttons.
  - New `feedback_archive_action` SSR handler at `POST /admin/feedback/:id/archive`, follows the same `from` hidden-field redirect pattern BUNYIP-92 introduced so the admin lands back on the originating tab with a `?toast_ok=Archived` toast.
- bunyip-web/src/main.rs: register the new route adjacent to mark-spam / unmark-spam / delete.

Naming: the existing `feedback_archive` GET handler (BUNYIP-85) lists archived rows. The new `feedback_archive_action` POST handler archives a single row. The two names differ so the router and the reader can tell them apart at a glance.

`just check-container` clean (modulo the pre-existing `test_config_defaults` parallel-env-var flake on main; 188 other tests pass; fmt + clippy + build clean).

#BUNYIP-93
YousifShkara deleted branch feat/feedback-admin-archive-and-reply-button 2026-06-11 07:39:09 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
psa-systems/bunyip!115
No description provided.