order-management 0.0.96 → 0.0.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/index.js +2675 -626
- package/.medusa/server/src/admin/index.mjs +2679 -630
- package/.medusa/server/src/api/admin/deleted-records-audit/[id]/route.js +30 -0
- package/.medusa/server/src/api/admin/deleted-records-audit/route.js +48 -0
- package/.medusa/server/src/api/admin/deleted-records-audit/stats/route.js +27 -0
- package/.medusa/server/src/api/admin/deleted-records-audit/validators.js +57 -0
- package/.medusa/server/src/modules/deleted-records-audit/audited-tables.js +257 -0
- package/.medusa/server/src/modules/deleted-records-audit/index.js +14 -0
- package/.medusa/server/src/modules/deleted-records-audit/migrations/Migration20261008120000.js +81 -0
- package/.medusa/server/src/modules/deleted-records-audit/models/deleted-records-audit.js +15 -0
- package/.medusa/server/src/modules/deleted-records-audit/service.js +164 -0
- package/.medusa/server/src/modules/deleted-records-audit/types.js +3 -0
- package/README.md +78 -0
- package/package.json +1 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
7
|
+
const deleted_records_audit_1 = __importDefault(require("./models/deleted-records-audit"));
|
|
8
|
+
const DELETED_BY_OPTIONS_LIMIT = 200;
|
|
9
|
+
function asString(value) {
|
|
10
|
+
if (value === null || value === undefined)
|
|
11
|
+
return null;
|
|
12
|
+
return String(value);
|
|
13
|
+
}
|
|
14
|
+
function asIso(value) {
|
|
15
|
+
if (value instanceof Date)
|
|
16
|
+
return value.toISOString();
|
|
17
|
+
const parsed = new Date(String(value));
|
|
18
|
+
return Number.isNaN(parsed.getTime()) ? String(value) : parsed.toISOString();
|
|
19
|
+
}
|
|
20
|
+
function asNumber(value) {
|
|
21
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
22
|
+
return Number.isFinite(n) ? n : 0;
|
|
23
|
+
}
|
|
24
|
+
function asObject(value) {
|
|
25
|
+
if (value === null || value === undefined)
|
|
26
|
+
return null;
|
|
27
|
+
if (typeof value === "string") {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(value);
|
|
30
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
31
|
+
? parsed
|
|
32
|
+
: { value: parsed };
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { value };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === "object" && !Array.isArray(value))
|
|
39
|
+
return value;
|
|
40
|
+
return { value };
|
|
41
|
+
}
|
|
42
|
+
function rowsOf(result) {
|
|
43
|
+
if (typeof result === "object" && result !== null && "rows" in result) {
|
|
44
|
+
const rows = result.rows;
|
|
45
|
+
return Array.isArray(rows) ? rows : [];
|
|
46
|
+
}
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
function escapeLike(value) {
|
|
50
|
+
return value.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
51
|
+
}
|
|
52
|
+
class DeletedRecordsAuditModuleService extends (0, utils_1.MedusaService)({
|
|
53
|
+
DeletedRecordsAudit: deleted_records_audit_1.default,
|
|
54
|
+
}) {
|
|
55
|
+
constructor(container) {
|
|
56
|
+
super(container);
|
|
57
|
+
this.manager_ = container.manager;
|
|
58
|
+
}
|
|
59
|
+
toListItem(row) {
|
|
60
|
+
return {
|
|
61
|
+
id: String(row.id),
|
|
62
|
+
table_name: String(row.table_name),
|
|
63
|
+
record_id: asString(row.record_id),
|
|
64
|
+
operation: asString(row.operation) ?? "DELETE",
|
|
65
|
+
occurred_at: asIso(row.occurred_at),
|
|
66
|
+
deleted_by: asString(row.deleted_by),
|
|
67
|
+
source: asString(row.source),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async listAudits(filters) {
|
|
71
|
+
const where = [];
|
|
72
|
+
const params = [];
|
|
73
|
+
const q = filters.q?.trim();
|
|
74
|
+
if (q) {
|
|
75
|
+
where.push(`record_id ILIKE ? ESCAPE '\\'`);
|
|
76
|
+
params.push(`%${escapeLike(q)}%`);
|
|
77
|
+
}
|
|
78
|
+
if (filters.table_names?.length) {
|
|
79
|
+
where.push(`table_name IN (${filters.table_names.map(() => "?").join(", ")})`);
|
|
80
|
+
params.push(...filters.table_names);
|
|
81
|
+
}
|
|
82
|
+
if (filters.date_from) {
|
|
83
|
+
where.push(`occurred_at >= ?`);
|
|
84
|
+
params.push(filters.date_from);
|
|
85
|
+
}
|
|
86
|
+
if (filters.date_to) {
|
|
87
|
+
where.push(`occurred_at <= ?`);
|
|
88
|
+
params.push(filters.date_to);
|
|
89
|
+
}
|
|
90
|
+
if (filters.source) {
|
|
91
|
+
if (filters.source === "unknown") {
|
|
92
|
+
where.push(`source IS NULL`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
where.push(`source = ?`);
|
|
96
|
+
params.push(filters.source);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const deletedBy = filters.deleted_by?.trim();
|
|
100
|
+
if (deletedBy) {
|
|
101
|
+
where.push(`deleted_by ILIKE ? ESCAPE '\\'`);
|
|
102
|
+
params.push(`%${escapeLike(deletedBy)}%`);
|
|
103
|
+
}
|
|
104
|
+
const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
|
|
105
|
+
const knex = this.manager_.getKnex();
|
|
106
|
+
const [listResult, countResult] = await Promise.all([
|
|
107
|
+
knex.raw(`SELECT id, table_name, record_id, operation, occurred_at, deleted_by, source
|
|
108
|
+
FROM deleted_records_audit
|
|
109
|
+
${whereSql}
|
|
110
|
+
ORDER BY occurred_at DESC, id DESC
|
|
111
|
+
LIMIT ? OFFSET ?`, [...params, filters.limit, filters.offset]),
|
|
112
|
+
knex.raw(`SELECT COUNT(*)::int AS count FROM deleted_records_audit ${whereSql}`, params),
|
|
113
|
+
]);
|
|
114
|
+
return {
|
|
115
|
+
audits: rowsOf(listResult).map((row) => this.toListItem(row)),
|
|
116
|
+
count: asNumber(rowsOf(countResult)[0]?.count),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async retrieveAudit(id) {
|
|
120
|
+
const knex = this.manager_.getKnex();
|
|
121
|
+
const result = await knex.raw(`SELECT id, table_name, record_id, operation, old_data, occurred_at, deleted_by, source
|
|
122
|
+
FROM deleted_records_audit
|
|
123
|
+
WHERE id = ?
|
|
124
|
+
LIMIT 1`, [id]);
|
|
125
|
+
const row = rowsOf(result)[0];
|
|
126
|
+
if (!row)
|
|
127
|
+
return null;
|
|
128
|
+
return { ...this.toListItem(row), old_data: asObject(row.old_data) };
|
|
129
|
+
}
|
|
130
|
+
async getStats(options = {}) {
|
|
131
|
+
const knex = this.manager_.getKnex();
|
|
132
|
+
const todayStartSql = options.today_start ? "?::timestamptz" : "date_trunc('day', now())";
|
|
133
|
+
const todayParams = options.today_start ? [options.today_start, options.today_start] : [];
|
|
134
|
+
const [totalsResult, sourcesResult, deletedByResult] = await Promise.all([
|
|
135
|
+
knex.raw(`SELECT
|
|
136
|
+
COUNT(*)::int AS total,
|
|
137
|
+
COUNT(*) FILTER (WHERE occurred_at >= ${todayStartSql})::int AS today,
|
|
138
|
+
COUNT(*) FILTER (WHERE occurred_at >= (${todayStartSql}) - interval '6 days')::int AS last_7_days,
|
|
139
|
+
COUNT(DISTINCT table_name)::int AS tables_affected
|
|
140
|
+
FROM deleted_records_audit`, todayParams),
|
|
141
|
+
knex.raw(`SELECT DISTINCT source FROM deleted_records_audit WHERE source IS NOT NULL ORDER BY source`),
|
|
142
|
+
knex.raw(`SELECT deleted_by FROM deleted_records_audit
|
|
143
|
+
WHERE deleted_by IS NOT NULL
|
|
144
|
+
GROUP BY deleted_by
|
|
145
|
+
ORDER BY MAX(occurred_at) DESC
|
|
146
|
+
LIMIT ?`, [DELETED_BY_OPTIONS_LIMIT]),
|
|
147
|
+
]);
|
|
148
|
+
const totals = rowsOf(totalsResult)[0] ?? {};
|
|
149
|
+
return {
|
|
150
|
+
total: asNumber(totals.total),
|
|
151
|
+
today: asNumber(totals.today),
|
|
152
|
+
last_7_days: asNumber(totals.last_7_days),
|
|
153
|
+
tables_affected: asNumber(totals.tables_affected),
|
|
154
|
+
sources: rowsOf(sourcesResult)
|
|
155
|
+
.map((r) => asString(r.source))
|
|
156
|
+
.filter((s) => Boolean(s)),
|
|
157
|
+
deleted_by: rowsOf(deletedByResult)
|
|
158
|
+
.map((r) => asString(r.deleted_by))
|
|
159
|
+
.filter((s) => Boolean(s)),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.default = DeletedRecordsAuditModuleService;
|
|
164
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL2RlbGV0ZWQtcmVjb3Jkcy1hdWRpdC9zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBQUEscURBQXlEO0FBRXpELDJGQUFnRTtBQWVoRSxNQUFNLHdCQUF3QixHQUFHLEdBQUcsQ0FBQTtBQUVwQyxTQUFTLFFBQVEsQ0FBQyxLQUFjO0lBQzlCLElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxLQUFLLEtBQUssU0FBUztRQUFFLE9BQU8sSUFBSSxDQUFBO0lBQ3RELE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFBO0FBQ3RCLENBQUM7QUFFRCxTQUFTLEtBQUssQ0FBQyxLQUFjO0lBQzNCLElBQUksS0FBSyxZQUFZLElBQUk7UUFBRSxPQUFPLEtBQUssQ0FBQyxXQUFXLEVBQUUsQ0FBQTtJQUNyRCxNQUFNLE1BQU0sR0FBRyxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQTtJQUN0QyxPQUFPLE1BQU0sQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRSxDQUFBO0FBQzlFLENBQUM7QUFFRCxTQUFTLFFBQVEsQ0FBQyxLQUFjO0lBQzlCLE1BQU0sQ0FBQyxHQUFHLE9BQU8sS0FBSyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUE7SUFDM0QsT0FBTyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtBQUNuQyxDQUFDO0FBRUQsU0FBUyxRQUFRLENBQUMsS0FBYztJQUM5QixJQUFJ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
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9kZWxldGVkLXJlY29yZHMtYXVkaXQvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
|
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ export default defineConfig({
|
|
|
47
47
|
orderManagement.modules["payment-detail"],
|
|
48
48
|
orderManagement.modules["refund-payment-mapping"],
|
|
49
49
|
orderManagement.modules["cancel-order-reason"],
|
|
50
|
+
orderManagement.modules["deleted-records-audit"],
|
|
50
51
|
],
|
|
51
52
|
})
|
|
52
53
|
```
|
|
@@ -179,6 +180,9 @@ Most production values are expected to be supplied via plugin options in `medusa
|
|
|
179
180
|
| GET | `/admin/orders/:order_id/cancel-reason-mapping` | Admin JWT | Cancel reason mapping for order. |
|
|
180
181
|
| PUT | `/admin/refund-payment-mapping/:return_id` | Admin JWT | Update `refund_mode`, `images`, `details`. |
|
|
181
182
|
| POST | `/admin/refund-payment-mapping/:return_id/mark-refunded` | Admin JWT | Set `is_refunded=true` (requires return `received`; 400 `NOT_ALLOWED` otherwise). |
|
|
183
|
+
| GET | `/admin/deleted-records-audit` | Admin JWT | Paginated hard-delete audit entries (no `old_data`). See [Deleted Records audit](#deleted-records-audit). |
|
|
184
|
+
| GET | `/admin/deleted-records-audit/stats` | Admin JWT | All-time counts for summary cards plus known `source` / `deleted_by` values. |
|
|
185
|
+
| GET | `/admin/deleted-records-audit/:id` | Admin JWT | One audit entry including the full `old_data` snapshot. |
|
|
182
186
|
|
|
183
187
|
Important endpoint examples:
|
|
184
188
|
|
|
@@ -208,6 +212,7 @@ curl "http://localhost:9000/admin/orders/order_123/refund-context" \
|
|
|
208
212
|
| `PaymentDetailModuleService` | Payment detail CRUD and default constraints | `createPaymentDetail`, `updatePaymentDetail`, `deletePaymentDetail`, `makeDefault`, `hasDefaultPayment`, `listPaymentDetailsByCustomer` |
|
|
209
213
|
| `RefundPaymentMappingModuleService` | Return-to-payment mapping management | `createMapping`, `getByReturnId`, `updatePaymentId`, `markRefunded`, `updateAdminFields` |
|
|
210
214
|
| `CancelOrderReasonModuleService` | Stores cancel reason used for an order | `assertReasonExists`, `recordOrderCancelReason`, `getMappingWithReasonLabel` |
|
|
215
|
+
| `DeletedRecordsAuditModuleService` | Reads the hard-delete audit trail | `listAudits`, `retrieveAudit`, `getStats` |
|
|
211
216
|
|
|
212
217
|
## Workflows & Steps (Medusa v2)
|
|
213
218
|
|
|
@@ -245,6 +250,7 @@ curl "http://localhost:9000/admin/orders/order_123/refund-context" \
|
|
|
245
250
|
| Swaps routes | `/swaps`, `/swaps/:id` | Exchange listing/detail pages | Search, filters, reject/cancel exchange; uses `/admin/swaps*`. |
|
|
246
251
|
| Payments routes | `/payments`, `/payments/:id` | Transaction list + detail pages | Filter transactions and inspect gateway payload. |
|
|
247
252
|
| Refunds routes | `/refunds`, `/refunds/:id` | Refund list + detail pages | Filter by status/provider/date/amount; view computed refund context. |
|
|
253
|
+
| Deleted Records routes | `/deleted-records-audit`, `/deleted-records-audit/:id` | Audit list + investigation detail | Summary cards, record ID search, entity/date/source/deleted-by filters in the URL; detail shows record summary, masked raw data and next steps. |
|
|
248
254
|
| `order-gift-items-widget` | `order.details.side.after` | Gift item widget | Reads gift metadata and downloads generated gift card image. |
|
|
249
255
|
|
|
250
256
|
## Models & Entities
|
|
@@ -254,6 +260,7 @@ curl "http://localhost:9000/admin/orders/order_123/refund-context" \
|
|
|
254
260
|
| `payment_detail` | `id`, `customer_id`, `type`, `detail_json`, `is_default` | Linked logically to Medusa customer via `customer_id`. |
|
|
255
261
|
| `refund_payment_mapping` | `id`, `return_id` (unique), `payment_id?`, `is_refunded`, `refund_mode?`, `images`, `details?` | Links a return to refund destination and admin reconciliation metadata. |
|
|
256
262
|
| `cancel_order_reason_mapping` | `id`, `order_id`, `reason_id` | Migration defines FK to `return_reason(id)`. |
|
|
263
|
+
| `deleted_records_audit` | `id`, `table_name`, `record_id?`, `operation`, `old_data` (jsonb), `occurred_at`, `deleted_by?`, `source?` | Append-only; written by Postgres `BEFORE DELETE` triggers, never by application code. |
|
|
257
264
|
|
|
258
265
|
## Use Cases & Examples
|
|
259
266
|
|
|
@@ -277,6 +284,8 @@ curl "http://localhost:9000/admin/orders/order_123/refund-context" \
|
|
|
277
284
|
| Guest return shows no payment method in Admin | Guest COD returns previously stored `payment_id=null` and guests could not create/link payment details | Upgrade this plugin. Pass `refund_payment` / `refund_payment_id` on guest return create, or use `POST /store/guest-orders/:id/payment-details` + `PUT .../returns/:return_id/refund-payment`. |
|
|
278
285
|
| Cannot set default payment via create/update | API intentionally blocks `is_default` in request body | Use `POST /store/payment-details/:id/make-default`. |
|
|
279
286
|
| No development stack traces in API errors | `NODE_ENV` not `development` | Set `NODE_ENV=development` locally when debugging. |
|
|
287
|
+
| Deleted Records page is empty after deletes | `deleted-records-audit` module not registered/migrated, or rows were soft-deleted (`deleted_at` set) rather than hard-deleted | Register the module and run `npx medusa db:migrate`. Only real SQL `DELETE`s are captured. |
|
|
288
|
+
| Deleted Records shows "Unknown" for Deleted by / Source | The app didn't set `app.audit_actor_id` / `app.audit_source` in the deleting transaction | Expected by default. See [Recording who deleted a record](#recording-who-deleted-a-record). |
|
|
280
289
|
|
|
281
290
|
|
|
282
291
|
<p align="center">
|
|
@@ -334,6 +343,7 @@ This plugin is compatible with Medusa v2 (e.g. `@medusajs/framework` and `@medus
|
|
|
334
343
|
- **Medusa workflows**: Customer returns use `createAndCompleteReturnOrderWorkflow`; customer exchanges use `beginExchangeOrderWorkflow`, `orderExchangeRequestItemReturnWorkflow`, `orderExchangeAddNewItemWorkflow`, `confirmExchangeRequestWorkflow`, and `cancelBeginOrderExchangeWorkflow`. Admin actions use `confirmReturnReceiveWorkflow`, `cancelReturnWorkflow`, and `cancelBeginOrderExchangeWorkflow`.
|
|
335
344
|
- **Payment details**: Custom module `payment_detail` for customer payment methods (UPI, bank, card). Store CRUD at `/store/payment-details`. Type-driven validation — UPI requires `upi_id`; bank requires `account_holder_name`, `bank_name`, `account_number`, `ifsc` (optional `branch_name`); card requires `card_holder_name`, `card_number`, `expiry_date`, `cvv`. One default per customer; return creation requires at least one default payment method.
|
|
336
345
|
- **Refund payment mapping**: Table `refund_payment_mapping` (one row per return: `return_id`, `payment_id`, `is_refunded`, `refund_mode`, `images`, `details`). A mapping is created automatically when a return is created. Customer can GET mapping (read-only for all fields) and PUT `payment_id` only while `is_refunded = false`; customer can also GET all mappings for an order to see if all refunds are successful. Only admin can set `is_refunded`, `refund_mode` (offline/online), `images`, and `details`.
|
|
346
|
+
- **Deleted Records audit**: Postgres `BEFORE DELETE` triggers on 190 commerce/plugin tables copy every hard-deleted row into `deleted_records_audit`. An Admin page (**Deleted Records**) lets admins search by record ID, filter by entity/date/source, read a human-readable summary of what was deleted, inspect the full (secret-masked) snapshot, and see what to do next. See [Deleted Records audit](#deleted-records-audit).
|
|
337
347
|
- **Receive-before-refund**: Admin payment refunds (`POST /admin/payments/:id/refund`) and `POST /admin/refund-payment-mapping/:return_id/mark-refunded` are rejected with `400 NOT_ALLOWED` while any return on the order is `open`, `requested`, or `partially_received` (including exchange-linked returns). Receive items first. Refunds with no unreceived return (order cancel / goodwill) still work. Receive-and-refund inside Medusa’s receive workflow is not blocked.
|
|
338
348
|
|
|
339
349
|
### Plugin structure (high level)
|
|
@@ -595,6 +605,74 @@ The plugin includes a `refund_payment_mapping` module: one row per return linkin
|
|
|
595
605
|
- **Store return payload**: `GET /store/returns` and `GET /store/returns/:id` attach the same `refund` object so the storefront can render the breakdown. `GET /store/orders/:order_id/refund-payment-mappings` includes `refund` per mapping.
|
|
596
606
|
- **Registration**: Add the module in `medusa-config.js` (e.g. `orderManagement.modules["refund-payment-mapping"]`) and run migrations.
|
|
597
607
|
|
|
608
|
+
### Deleted Records audit
|
|
609
|
+
|
|
610
|
+
A hard `DELETE` normally leaves no trace. The `deleted_records_audit` module installs a Postgres trigger on each monitored table that copies the full row into `deleted_records_audit` **inside the same transaction** as the delete, so rolled-back deletes leave no audit row and nothing can be deleted without being recorded.
|
|
611
|
+
|
|
612
|
+
**Setup**
|
|
613
|
+
|
|
614
|
+
1. Register `orderManagement.modules["deleted-records-audit"]` in `modules` (see [Installation & Setup](#installation--setup)).
|
|
615
|
+
2. Run `npx medusa db:migrate`.
|
|
616
|
+
|
|
617
|
+
The migration (`Migration20261008120000`) creates the table, the `om_audit_row_delete()` function, and one `om_ad_<table>` trigger per table in `src/modules/deleted-records-audit/audited-tables.ts` (190 tables covering orders, carts, customers, catalog, pricing, payments, fulfillment, inventory, wallet, auth, store settings, engagement, notifications and system tables). Tables that don't exist in your database are skipped, so the migration is safe on smaller installs. Re-running it is idempotent; rolling it back removes every trigger, the function and the table.
|
|
618
|
+
|
|
619
|
+
**What is captured**
|
|
620
|
+
|
|
621
|
+
| Column | Meaning |
|
|
622
|
+
|---|---|
|
|
623
|
+
| `table_name` | Table the row was deleted from. Shown in the UI as a human entity name (e.g. `order_line_item` → "Order Item"). |
|
|
624
|
+
| `record_id` | The deleted row's `id`. `null` for link tables without an `id` column. |
|
|
625
|
+
| `old_data` | Full row as JSON, exactly as it was immediately before deletion. |
|
|
626
|
+
| `occurred_at` | When the delete ran. (Not `deleted_at`: that column is Medusa's soft-delete marker.) |
|
|
627
|
+
| `deleted_by`, `source` | Optional attribution—see below. |
|
|
628
|
+
|
|
629
|
+
Soft deletes (`UPDATE … SET deleted_at = now()`) are **not** captured; only real SQL `DELETE`s are.
|
|
630
|
+
|
|
631
|
+
#### Recording who deleted a record
|
|
632
|
+
|
|
633
|
+
The trigger reads two Postgres settings from the deleting transaction. If your code sets them, the UI shows who deleted the record and where the deletion came from; otherwise both show "Unknown".
|
|
634
|
+
|
|
635
|
+
```sql
|
|
636
|
+
BEGIN;
|
|
637
|
+
SET LOCAL app.audit_actor_id = 'user_01H...'; -- shown as "Deleted by"
|
|
638
|
+
SET LOCAL app.audit_source = 'admin'; -- admin | api | system | job | workflow | any label
|
|
639
|
+
DELETE FROM product WHERE id = 'prod_123';
|
|
640
|
+
COMMIT;
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
`SET LOCAL` only lasts until the end of the transaction, so the values cannot leak into other requests on a pooled connection.
|
|
644
|
+
|
|
645
|
+
#### Admin UI
|
|
646
|
+
|
|
647
|
+
**Deleted Records** (`/app/deleted-records-audit`) is an investigation tool rather than a raw table viewer:
|
|
648
|
+
|
|
649
|
+
- **Summary cards**: total deleted, today, last 7 days, and the number of entity types affected (all-time, computed from real data).
|
|
650
|
+
- **Search** by record ID (substring, case-insensitive).
|
|
651
|
+
- **Filters**: a searchable, grouped **Entity** picker (pick a whole area such as "All Orders" or a single entity), a **Date** preset (any time, today, yesterday, last 7/30 days, custom range), plus **Source** and **Deleted by** filters, which appear once attribution data exists. Filters live in the URL, so investigations can be bookmarked or shared, and going back from a detail page restores the same filters, loaded rows and position.
|
|
652
|
+
- **Detail page** (`/app/deleted-records-audit/:id`): entity and record ID, a clear *historical snapshot* notice, deletion time/actor/source, a **Record summary** of the important fields for common entities (orders, items, customers, products, payments, refunds, returns, carts, users, API keys, promotions, inventory, reviews…), the full snapshot in a collapsible JSON viewer with **Copy JSON**, and **What you can do now** guidance tailored to the kind of record.
|
|
653
|
+
|
|
654
|
+
There is deliberately **no restore button**: recreating records automatically is unsafe for linked commerce data. The guidance explains how to recreate a record when that's appropriate (and when not to, e.g. payments or wallet balances).
|
|
655
|
+
|
|
656
|
+
**Sensitive data**: passwords, hashes, tokens, secrets, API keys, card/account numbers, CVVs, OTP and recovery codes are masked (`••••••••••••`) in the summary, the JSON viewer and **Copy JSON**. The raw data in the database is unmasked, and the API routes are admin-only.
|
|
657
|
+
|
|
658
|
+
**List API query parameters** (`GET /admin/deleted-records-audit`)
|
|
659
|
+
|
|
660
|
+
| Param | Description |
|
|
661
|
+
|---|---|
|
|
662
|
+
| `limit` (≤ 100, default 50), `offset` | Pagination. Response: `{ audits, count, limit, offset }`. |
|
|
663
|
+
| `q` | Record ID substring (`%` / `_` are matched literally). |
|
|
664
|
+
| `table_name` / `table_names` | One table, or a comma-separated list (used for category filters). |
|
|
665
|
+
| `date_from`, `date_to` | ISO timestamps bounding `occurred_at`. |
|
|
666
|
+
| `source` | Exact source value; `unknown` matches rows with no source. |
|
|
667
|
+
| `deleted_by` | Substring match on `deleted_by`. |
|
|
668
|
+
|
|
669
|
+
`GET /admin/deleted-records-audit/stats?today_start=<ISO>` returns `{ stats: { total, today, last_7_days, tables_affected, sources, deleted_by } }`. Pass the viewer's local midnight as `today_start` so "Today" matches their timezone.
|
|
670
|
+
|
|
671
|
+
**Operational notes**
|
|
672
|
+
|
|
673
|
+
- Every hard delete on a monitored table writes one extra row. High-churn tables (`cart_*`, `workflow_execution`, `notification`, search index tables) can grow the audit table quickly, so plan retention or archiving for large stores.
|
|
674
|
+
- To monitor another table, add it to `AUDITED_TABLE_NAMES` and ship a new migration that calls `buildCreateAuditTriggerSql(table)`. Never add `deleted_records_audit` itself.
|
|
675
|
+
|
|
598
676
|
|
|
599
677
|
## Return Orders Admin Panel
|
|
600
678
|
|