Skip to content

Commit 2ed975a

Browse files
hvn2k1claude
andcommitted
refactor(notification): switch delete admin notification to soft delete
- Add a deleted flag to the notification table (MySQL + Postgres migrations); localizations and read markers stay intact on delete - DeleteAdminNotification now flags the row and records who deleted it and when via last_edited_by/updated_at - Exclude soft-deleted rows from get, update, and draft listing queries Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d996382 commit 2ed975a

22 files changed

Lines changed: 135 additions & 87 deletions

api-description/web-api.swagger.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1230,7 +1230,7 @@ paths:
12301230
/v1/admin_notification:
12311231
delete:
12321232
summary: Delete
1233-
description: Delete a notification along with its read markers. System admin only.
1233+
description: Soft-delete a notification; it disappears from all listings but its data is retained. System admin only.
12341234
operationId: web.v1.notification.admin.delete
12351235
responses:
12361236
"200":
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Soft-delete support for the notification center: deleted notifications are
2+
-- flagged instead of removed so their localizations and read markers stay
3+
-- intact until the admin audit log is implemented.
4+
ALTER TABLE notification ADD COLUMN deleted BOOL NOT NULL DEFAULT 0;

migration/mysql/atlas.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
h1:lZ6N6pD7g4R1o3ey4lmh5oCyVrOxGtwVjen4h+WqiZo=
1+
h1:8YDpIU578LqzbUEvkNKDitZCF1LqIotUqMUsDTn5g08=
22
20240626022133_initialization.sql h1:reSmqMhqnsrdIdPU2ezv/PXSL0COlRFX4gQA4U3/wMo=
33
20240708065726_update_audit_log_table.sql h1:fi8Xxw4WfSlHDyvq2Ni/8JUiZW8z/0qWWyWm6jFdUy8=
44
20240815043128_update_auto_ops_rule_table.sql h1:IKSW9W/XO6SWAYl5WPLJSg6KdsfcZ3rfQhIrf7aOnYc=
@@ -42,3 +42,4 @@ h1:lZ6N6pD7g4R1o3ey4lmh5oCyVrOxGtwVjen4h+WqiZo=
4242
20260316000000_update_auto_archive_default_days.sql h1:pq20H9ieN5314aAbM9JH29xvFPjj93xoUSmluEZICxw=
4343
20260514000000_update_feature_variation_value_schema.sql h1:ocGacenoNr4+sVeCeWASFDnUn+Sn2n04XmZpSf+82qM=
4444
20260713000000_create_notification_tables.sql h1:87SKJUcoNMMv48953z5QyaS9c/AxzJamnf+SZ6/SMJU=
45+
20260728000000_add_notification_deleted.sql h1:yky/5yXtjVf4Vl9si/FJ5ODUSPzjC1NfUsyfLCTh97I=
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Soft-delete support for the notification center: deleted notifications are
2+
-- flagged instead of removed so their localizations and read markers stay
3+
-- intact until the admin audit log is implemented.
4+
ALTER TABLE notification ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE;

migration/postgres/atlas.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
h1:A4Op2+FtS//RFAl45dYO66IFvB9Pcad7eGv/Nn1BoDg=
1+
h1:s6kmbtFcVifKk1JOyBlaUyVfQtEFuVNgbbGIlTM7A10=
22
20260226174000_initialization.sql h1:orWPjklxeOP046jFps+1UhJDdaSDPwDjlODiSe/479c=
33
20260514000000_update_feature_variation_value_schema.sql h1:Jp91HETgQvAvqNGTgSBip8ipx3aAI5C4Tsa2z8eplB4=
44
20260713000000_create_notification_tables.sql h1:TqsueyglKP41Towy2FsYTGyxI3+h4bRbpGS4MZLLNhw=
5+
20260728000000_add_notification_deleted.sql h1:OqtTB/u1YAJXjuhwfCJjfTfuNuaLtSvbUvbXD/82L6E=

pkg/notification/api/api.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"errors"
2020
"strconv"
2121
"strings"
22+
"time"
2223

2324
"go.uber.org/zap"
2425
"google.golang.org/grpc"
@@ -327,15 +328,15 @@ func (s *NotificationService) DeleteAdminNotification(
327328
ctx context.Context,
328329
req *proto.DeleteAdminNotificationRequest,
329330
) (*proto.DeleteAdminNotificationResponse, error) {
330-
_, err := s.checkSystemAdminRole(ctx)
331+
editor, err := s.checkSystemAdminRole(ctx)
331332
if err != nil {
332333
return nil, err
333334
}
334335
if len(strings.TrimSpace(req.Id)) == 0 {
335336
return nil, statusNotificationIDRequired.Err()
336337
}
337338
err = s.dbClient.RunInTransactionV2(ctx, func(ctxWithTx context.Context) error {
338-
return s.notificationStorage.DeleteAdminNotification(ctxWithTx, req.Id)
339+
return s.notificationStorage.DeleteAdminNotification(ctxWithTx, req.Id, editor.Email, time.Now().Unix())
339340
})
340341
if err != nil {
341342
if errors.Is(err, storage.ErrNotificationNotFound) {

pkg/notification/api/api_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ func TestNotificationService_DeleteAdminNotification(t *testing.T) {
639639
return fn(ctx)
640640
})
641641
s.notificationStorage.(*notificationstoragemock.MockNotificationStorage).EXPECT().DeleteAdminNotification(
642-
gomock.Any(), "notification-id-0",
642+
gomock.Any(), "notification-id-0", "email", gomock.Any(),
643643
).Return(storage.ErrNotificationNotFound)
644644
},
645645
req: &proto.DeleteAdminNotificationRequest{
@@ -670,7 +670,7 @@ func TestNotificationService_DeleteAdminNotification(t *testing.T) {
670670
return fn(ctx)
671671
})
672672
s.notificationStorage.(*notificationstoragemock.MockNotificationStorage).EXPECT().DeleteAdminNotification(
673-
gomock.Any(), "notification-id-0",
673+
gomock.Any(), "notification-id-0", "email", gomock.Any(),
674674
).Return(nil)
675675
},
676676
req: &proto.DeleteAdminNotificationRequest{

pkg/notification/storage/mock/notification.go

Lines changed: 5 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/notification/storage/mysql/notification.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,14 @@ func (s *notificationStorage) UpdateAdminNotification(
175175

176176
func (s *notificationStorage) DeleteAdminNotification(
177177
ctx context.Context,
178-
id string,
178+
id, lastEditedBy string,
179+
updatedAt int64,
179180
) error {
180181
result, err := s.qe.ExecContext(
181182
ctx,
182183
deleteNotificationSQL,
184+
lastEditedBy,
185+
updatedAt,
183186
id,
184187
)
185188
if err != nil {
@@ -225,6 +228,11 @@ func listDraftAdminNotificationsFilters(
225228
Operator: mysqlstorage.OperatorEqual,
226229
Value: int32(proto.Notification_DRAFT),
227230
},
231+
{
232+
Column: "notification.deleted",
233+
Operator: mysqlstorage.OperatorEqual,
234+
Value: false,
235+
},
228236
}
229237
var searchQuery *mysqlstorage.SearchQuery
230238
if searchKeyword != "" {

pkg/notification/storage/mysql/notification_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,8 @@ func TestDeleteAdminNotification(t *testing.T) {
606606
s.qe.(*mock.MockQueryExecer).EXPECT().ExecContext(
607607
gomock.Any(),
608608
deleteNotificationSQL,
609+
"editor@example.com",
610+
int64(5),
609611
"notification-id-0",
610612
).Return(result, nil)
611613
},
@@ -619,7 +621,7 @@ func TestDeleteAdminNotification(t *testing.T) {
619621
if p.setup != nil {
620622
p.setup(storage)
621623
}
622-
err := storage.DeleteAdminNotification(context.Background(), p.id)
624+
err := storage.DeleteAdminNotification(context.Background(), p.id, "editor@example.com", 5)
623625
assert.Equal(t, p.expectedErr, err)
624626
})
625627
}

0 commit comments

Comments
 (0)