@orthacms/activity-server 0.0.0-reserve.0
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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/lib/activity/activity-filter.d.ts +14 -0
- package/dist/lib/activity/activity-filter.d.ts.map +1 -0
- package/dist/lib/activity/activity-filter.js +25 -0
- package/dist/lib/activity/activity.constants.d.ts +18 -0
- package/dist/lib/activity/activity.constants.d.ts.map +1 -0
- package/dist/lib/activity/activity.constants.js +16 -0
- package/dist/lib/activity/controllers/list-activity.controller.d.ts +14 -0
- package/dist/lib/activity/controllers/list-activity.controller.d.ts.map +1 -0
- package/dist/lib/activity/controllers/list-activity.controller.js +36 -0
- package/dist/lib/activity/dto/list-activity-query.dto.d.ts +46 -0
- package/dist/lib/activity/dto/list-activity-query.dto.d.ts.map +1 -0
- package/dist/lib/activity/dto/list-activity-query.dto.js +197 -0
- package/dist/lib/activity/infrastructure/audit-event-mapping.d.ts +60 -0
- package/dist/lib/activity/infrastructure/audit-event-mapping.d.ts.map +1 -0
- package/dist/lib/activity/infrastructure/audit-event-mapping.js +402 -0
- package/dist/lib/activity/infrastructure/audit-event.subscriber.d.ts +29 -0
- package/dist/lib/activity/infrastructure/audit-event.subscriber.d.ts.map +1 -0
- package/dist/lib/activity/infrastructure/audit-event.subscriber.js +54 -0
- package/dist/lib/activity/services/activity.service.d.ts +55 -0
- package/dist/lib/activity/services/activity.service.d.ts.map +1 -0
- package/dist/lib/activity/services/activity.service.js +147 -0
- package/dist/lib/activity/types/activity-view.d.ts +37 -0
- package/dist/lib/activity/types/activity-view.d.ts.map +1 -0
- package/dist/lib/activity/types/activity-view.js +2 -0
- package/dist/lib/activity.module.d.ts +20 -0
- package/dist/lib/activity.module.d.ts.map +1 -0
- package/dist/lib/activity.module.js +49 -0
- package/dist/lib/copilot/activity-tool.provider.d.ts +40 -0
- package/dist/lib/copilot/activity-tool.provider.d.ts.map +1 -0
- package/dist/lib/copilot/activity-tool.provider.js +157 -0
- package/dist/lib/schema/activity-events.d.ts +178 -0
- package/dist/lib/schema/activity-events.d.ts.map +1 -0
- package/dist/lib/schema/activity-events.js +38 -0
- package/dist/lib/schema/index.d.ts +2 -0
- package/dist/lib/schema/index.d.ts.map +1 -0
- package/dist/lib/schema/index.js +5 -0
- package/dist/lib/utils/activity-plugin.d.ts +26 -0
- package/dist/lib/utils/activity-plugin.d.ts.map +1 -0
- package/dist/lib/utils/activity-plugin.js +38 -0
- package/migrations/0000_init.sql +15 -0
- package/migrations/meta/0000_snapshot.json +159 -0
- package/migrations/meta/_journal.json +13 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ortha CMS contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { ActivityPlugin } from './lib/utils/activity-plugin';
|
|
2
|
+
export { ActivityModule } from './lib/activity.module';
|
|
3
|
+
export { ActivityService } from './lib/activity/services/activity.service';
|
|
4
|
+
export { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, SORTABLE_FIELDS, SORT_ORDERS } from './lib/activity/activity.constants';
|
|
5
|
+
export type { SortableField, SortOrder } from './lib/activity/activity.constants';
|
|
6
|
+
export type { ActivityEventView, ActivityListView } from './lib/activity/types/activity-view';
|
|
7
|
+
export { activityEvents } from './lib/schema';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAC3E,OAAO,EACH,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,WAAW,EACd,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EACR,aAAa,EACb,SAAS,EACZ,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EACR,iBAAiB,EACjB,gBAAgB,EACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.activityEvents = exports.SORT_ORDERS = exports.SORTABLE_FIELDS = exports.MAX_PAGE_SIZE = exports.DEFAULT_PAGE_SIZE = exports.ActivityService = exports.ActivityModule = exports.ActivityPlugin = void 0;
|
|
4
|
+
var activity_plugin_1 = require("./lib/utils/activity-plugin");
|
|
5
|
+
Object.defineProperty(exports, "ActivityPlugin", { enumerable: true, get: function () { return activity_plugin_1.ActivityPlugin; } });
|
|
6
|
+
var activity_module_1 = require("./lib/activity.module");
|
|
7
|
+
Object.defineProperty(exports, "ActivityModule", { enumerable: true, get: function () { return activity_module_1.ActivityModule; } });
|
|
8
|
+
var activity_service_1 = require("./lib/activity/services/activity.service");
|
|
9
|
+
Object.defineProperty(exports, "ActivityService", { enumerable: true, get: function () { return activity_service_1.ActivityService; } });
|
|
10
|
+
var activity_constants_1 = require("./lib/activity/activity.constants");
|
|
11
|
+
Object.defineProperty(exports, "DEFAULT_PAGE_SIZE", { enumerable: true, get: function () { return activity_constants_1.DEFAULT_PAGE_SIZE; } });
|
|
12
|
+
Object.defineProperty(exports, "MAX_PAGE_SIZE", { enumerable: true, get: function () { return activity_constants_1.MAX_PAGE_SIZE; } });
|
|
13
|
+
Object.defineProperty(exports, "SORTABLE_FIELDS", { enumerable: true, get: function () { return activity_constants_1.SORTABLE_FIELDS; } });
|
|
14
|
+
Object.defineProperty(exports, "SORT_ORDERS", { enumerable: true, get: function () { return activity_constants_1.SORT_ORDERS; } });
|
|
15
|
+
var schema_1 = require("./lib/schema");
|
|
16
|
+
Object.defineProperty(exports, "activityEvents", { enumerable: true, get: function () { return schema_1.activityEvents; } });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FilterSchema } from '@orthacms/utils-server';
|
|
2
|
+
/**
|
|
3
|
+
* The filterable surface of the audit log for the query-builder engine.
|
|
4
|
+
* Field keys mirror the `activity_events` column property names so the
|
|
5
|
+
* translator can resolve each to its Drizzle column. Deliberately omits
|
|
6
|
+
* `meta` (open jsonb) and `createdAt` (kept off the wire) — only the
|
|
7
|
+
* indexed, frozen-snapshot columns are exposed.
|
|
8
|
+
*
|
|
9
|
+
* A user-supplied `?filter=` is validated against this schema and then
|
|
10
|
+
* AND-ed with the structured query params (`subjectType`, `kind`, …) the
|
|
11
|
+
* controller already accepts, so the two filter surfaces compose.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ACTIVITY_FILTER_SCHEMA: FilterSchema;
|
|
14
|
+
//# sourceMappingURL=activity-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-filter.d.ts","sourceRoot":"","sources":["../../../src/lib/activity/activity-filter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE5E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,EAAE,YASpC,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ACTIVITY_FILTER_SCHEMA = void 0;
|
|
4
|
+
const utils_server_1 = require("@orthacms/utils-server");
|
|
5
|
+
/**
|
|
6
|
+
* The filterable surface of the audit log for the query-builder engine.
|
|
7
|
+
* Field keys mirror the `activity_events` column property names so the
|
|
8
|
+
* translator can resolve each to its Drizzle column. Deliberately omits
|
|
9
|
+
* `meta` (open jsonb) and `createdAt` (kept off the wire) — only the
|
|
10
|
+
* indexed, frozen-snapshot columns are exposed.
|
|
11
|
+
*
|
|
12
|
+
* A user-supplied `?filter=` is validated against this schema and then
|
|
13
|
+
* AND-ed with the structured query params (`subjectType`, `kind`, …) the
|
|
14
|
+
* controller already accepts, so the two filter surfaces compose.
|
|
15
|
+
*/
|
|
16
|
+
exports.ACTIVITY_FILTER_SCHEMA = {
|
|
17
|
+
fields: {
|
|
18
|
+
kind: { type: utils_server_1.ScalarFieldType.String },
|
|
19
|
+
subjectType: { type: utils_server_1.ScalarFieldType.String },
|
|
20
|
+
subjectId: { type: utils_server_1.ScalarFieldType.String },
|
|
21
|
+
actorId: { type: utils_server_1.ScalarFieldType.Uuid },
|
|
22
|
+
actorEmail: { type: utils_server_1.ScalarFieldType.String },
|
|
23
|
+
at: { type: utils_server_1.ScalarFieldType.Date }
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Default page size for `GET /api/activity`. */
|
|
2
|
+
export declare const DEFAULT_PAGE_SIZE = 25;
|
|
3
|
+
/** Upper bound on the requested page size (the largest option the UI offers). */
|
|
4
|
+
export declare const MAX_PAGE_SIZE = 100;
|
|
5
|
+
/**
|
|
6
|
+
* Max length of the raw `?filter=` JSON string. A coarse first guard against
|
|
7
|
+
* oversized payloads, ahead of the filter engine's node/depth caps.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FILTER_MAX_LENGTH = 4096;
|
|
10
|
+
/** Columns the read API permits sorting by (whitelist — keys are the wire values). */
|
|
11
|
+
export declare const SORTABLE_FIELDS: readonly ["at", "kind"];
|
|
12
|
+
/** A field the read API may sort by. */
|
|
13
|
+
export type SortableField = (typeof SORTABLE_FIELDS)[number];
|
|
14
|
+
/** Sort directions the read API accepts. */
|
|
15
|
+
export declare const SORT_ORDERS: readonly ["asc", "desc"];
|
|
16
|
+
/** A sort direction accepted by the read API. */
|
|
17
|
+
export type SortOrder = (typeof SORT_ORDERS)[number];
|
|
18
|
+
//# sourceMappingURL=activity.constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity.constants.d.ts","sourceRoot":"","sources":["../../../src/lib/activity/activity.constants.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,iFAAiF;AACjF,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC;;;GAGG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,sFAAsF;AACtF,eAAO,MAAM,eAAe,yBAA0B,CAAC;AAEvD,wCAAwC;AACxC,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,4CAA4C;AAC5C,eAAO,MAAM,WAAW,0BAA2B,CAAC;AAEpD,iDAAiD;AACjD,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SORT_ORDERS = exports.SORTABLE_FIELDS = exports.FILTER_MAX_LENGTH = exports.MAX_PAGE_SIZE = exports.DEFAULT_PAGE_SIZE = void 0;
|
|
4
|
+
/** Default page size for `GET /api/activity`. */
|
|
5
|
+
exports.DEFAULT_PAGE_SIZE = 25;
|
|
6
|
+
/** Upper bound on the requested page size (the largest option the UI offers). */
|
|
7
|
+
exports.MAX_PAGE_SIZE = 100;
|
|
8
|
+
/**
|
|
9
|
+
* Max length of the raw `?filter=` JSON string. A coarse first guard against
|
|
10
|
+
* oversized payloads, ahead of the filter engine's node/depth caps.
|
|
11
|
+
*/
|
|
12
|
+
exports.FILTER_MAX_LENGTH = 4096;
|
|
13
|
+
/** Columns the read API permits sorting by (whitelist — keys are the wire values). */
|
|
14
|
+
exports.SORTABLE_FIELDS = ['at', 'kind'];
|
|
15
|
+
/** Sort directions the read API accepts. */
|
|
16
|
+
exports.SORT_ORDERS = ['asc', 'desc'];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ActivityService } from '../services/activity.service';
|
|
2
|
+
import { ListActivityQueryDto } from '../dto/list-activity-query.dto';
|
|
3
|
+
import type { ActivityListView } from '../types/activity-view';
|
|
4
|
+
/**
|
|
5
|
+
* `GET /api/activity` — the global audit log. Read-only; gated on
|
|
6
|
+
* `activity:read` (admin-only by the v1 role matrix). All filtering,
|
|
7
|
+
* pagination, and sorting is delegated to {@link ActivityService.list}.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ListActivityController {
|
|
10
|
+
private readonly activity;
|
|
11
|
+
constructor(activity: ActivityService);
|
|
12
|
+
list(query: ListActivityQueryDto): Promise<ActivityListView>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=list-activity.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-activity.controller.d.ts","sourceRoot":"","sources":["../../../../src/lib/activity/controllers/list-activity.controller.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;;GAIG;AACH,qBAGa,sBAAsB;IACnB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,eAAe;IAGtD,IAAI,CAAU,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAGxE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ListActivityController = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
7
|
+
const activity_service_1 = require("../services/activity.service");
|
|
8
|
+
const list_activity_query_dto_1 = require("../dto/list-activity-query.dto");
|
|
9
|
+
/**
|
|
10
|
+
* `GET /api/activity` — the global audit log. Read-only; gated on
|
|
11
|
+
* `activity:read` (admin-only by the v1 role matrix). All filtering,
|
|
12
|
+
* pagination, and sorting is delegated to {@link ActivityService.list}.
|
|
13
|
+
*/
|
|
14
|
+
let ListActivityController = class ListActivityController {
|
|
15
|
+
activity;
|
|
16
|
+
constructor(activity) {
|
|
17
|
+
this.activity = activity;
|
|
18
|
+
}
|
|
19
|
+
list(query) {
|
|
20
|
+
return this.activity.list(query);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
exports.ListActivityController = ListActivityController;
|
|
24
|
+
tslib_1.__decorate([
|
|
25
|
+
(0, common_1.Get)(),
|
|
26
|
+
tslib_1.__param(0, (0, common_1.Query)()),
|
|
27
|
+
tslib_1.__metadata("design:type", Function),
|
|
28
|
+
tslib_1.__metadata("design:paramtypes", [list_activity_query_dto_1.ListActivityQueryDto]),
|
|
29
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
30
|
+
], ListActivityController.prototype, "list", null);
|
|
31
|
+
exports.ListActivityController = ListActivityController = tslib_1.__decorate([
|
|
32
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard),
|
|
33
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.ACTIVITY_READ),
|
|
34
|
+
(0, common_1.Controller)('activity'),
|
|
35
|
+
tslib_1.__metadata("design:paramtypes", [activity_service_1.ActivityService])
|
|
36
|
+
], ListActivityController);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type SortableField, type SortOrder } from '../activity.constants';
|
|
2
|
+
/**
|
|
3
|
+
* Query parameters for `GET /api/activity`. All filters are optional and
|
|
4
|
+
* intersected (AND). Pagination is 1-based; the service applies the defaults
|
|
5
|
+
* (page 1, {@link DEFAULT_PAGE_SIZE}) so the contract has one source of truth.
|
|
6
|
+
* `@Type`/`@Transform` coerce the raw query strings — the host's global
|
|
7
|
+
* `ValidationPipe` transforms but does not implicitly convert.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ListActivityQueryDto {
|
|
10
|
+
/** Restrict to one subject kind (e.g. `'user'`). */
|
|
11
|
+
subjectType?: string;
|
|
12
|
+
/** Restrict to one subject id (a specific entity's history). */
|
|
13
|
+
subjectId?: string;
|
|
14
|
+
/** Restrict to one actor. */
|
|
15
|
+
actorId?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Restrict to one or more event kinds. Accepts a comma-separated list
|
|
18
|
+
* (`?kind=user.invited,user.suspended`) → matched with `IN`. Kinds are
|
|
19
|
+
* owned by the emitting plugins, so this is validated as free text, not
|
|
20
|
+
* against a central catalogue.
|
|
21
|
+
*/
|
|
22
|
+
kind?: string[];
|
|
23
|
+
/** Case-insensitive substring search over the actor email snapshot. */
|
|
24
|
+
actorEmail?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Structured filter tree as a JSON string (`?filter=<json>`), produced by
|
|
27
|
+
* the admin query builder and validated against the activity filter schema
|
|
28
|
+
* by `parseFilterTree`. Length-capped here as a first line of defence; the
|
|
29
|
+
* engine's node/depth caps bound the parsed shape. AND-ed with the other
|
|
30
|
+
* params.
|
|
31
|
+
*/
|
|
32
|
+
filter?: string;
|
|
33
|
+
/** Inclusive lower bound on the event time (ISO 8601). */
|
|
34
|
+
from?: string;
|
|
35
|
+
/** Inclusive upper bound on the event time (ISO 8601). */
|
|
36
|
+
to?: string;
|
|
37
|
+
/** 1-based page number. */
|
|
38
|
+
page?: number;
|
|
39
|
+
/** Rows per page, capped at {@link MAX_PAGE_SIZE}. */
|
|
40
|
+
pageSize?: number;
|
|
41
|
+
/** Column to sort by (whitelisted). Defaults to `at`. */
|
|
42
|
+
sort?: SortableField;
|
|
43
|
+
/** Sort direction. Defaults to `desc`. */
|
|
44
|
+
order?: SortOrder;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=list-activity-query.dto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-activity-query.dto.d.ts","sourceRoot":"","sources":["../../../../src/lib/activity/dto/list-activity-query.dto.ts"],"names":[],"mappings":"AAcA,OAAO,EAMH,KAAK,aAAa,EAClB,KAAK,SAAS,EACjB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;GAMG;AACH,qBAAa,oBAAoB;IAC7B,oDAAoD;IAUpD,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,gEAAgE;IAUhE,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,6BAA6B;IAQ7B,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IAmBH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB,uEAAuE;IAUvE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;OAMG;IAYH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,0DAA0D;IAS1D,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,0DAA0D;IAS1D,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,2BAA2B;IAW3B,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,sDAAsD;IAatD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,yDAAyD;IAQzD,IAAI,CAAC,EAAE,aAAa,CAAC;IAErB,0CAA0C;IAQ1C,KAAK,CAAC,EAAE,SAAS,CAAC;CACrB"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ListActivityQueryDto = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
6
|
+
const class_transformer_1 = require("class-transformer");
|
|
7
|
+
const class_validator_1 = require("class-validator");
|
|
8
|
+
const activity_constants_1 = require("../activity.constants");
|
|
9
|
+
/**
|
|
10
|
+
* Query parameters for `GET /api/activity`. All filters are optional and
|
|
11
|
+
* intersected (AND). Pagination is 1-based; the service applies the defaults
|
|
12
|
+
* (page 1, {@link DEFAULT_PAGE_SIZE}) so the contract has one source of truth.
|
|
13
|
+
* `@Type`/`@Transform` coerce the raw query strings — the host's global
|
|
14
|
+
* `ValidationPipe` transforms but does not implicitly convert.
|
|
15
|
+
*/
|
|
16
|
+
class ListActivityQueryDto {
|
|
17
|
+
/** Restrict to one subject kind (e.g. `'user'`). */
|
|
18
|
+
subjectType;
|
|
19
|
+
/** Restrict to one subject id (a specific entity's history). */
|
|
20
|
+
subjectId;
|
|
21
|
+
/** Restrict to one actor. */
|
|
22
|
+
actorId;
|
|
23
|
+
/**
|
|
24
|
+
* Restrict to one or more event kinds. Accepts a comma-separated list
|
|
25
|
+
* (`?kind=user.invited,user.suspended`) → matched with `IN`. Kinds are
|
|
26
|
+
* owned by the emitting plugins, so this is validated as free text, not
|
|
27
|
+
* against a central catalogue.
|
|
28
|
+
*/
|
|
29
|
+
kind;
|
|
30
|
+
/** Case-insensitive substring search over the actor email snapshot. */
|
|
31
|
+
actorEmail;
|
|
32
|
+
/**
|
|
33
|
+
* Structured filter tree as a JSON string (`?filter=<json>`), produced by
|
|
34
|
+
* the admin query builder and validated against the activity filter schema
|
|
35
|
+
* by `parseFilterTree`. Length-capped here as a first line of defence; the
|
|
36
|
+
* engine's node/depth caps bound the parsed shape. AND-ed with the other
|
|
37
|
+
* params.
|
|
38
|
+
*/
|
|
39
|
+
filter;
|
|
40
|
+
/** Inclusive lower bound on the event time (ISO 8601). */
|
|
41
|
+
from;
|
|
42
|
+
/** Inclusive upper bound on the event time (ISO 8601). */
|
|
43
|
+
to;
|
|
44
|
+
/** 1-based page number. */
|
|
45
|
+
page;
|
|
46
|
+
/** Rows per page, capped at {@link MAX_PAGE_SIZE}. */
|
|
47
|
+
pageSize;
|
|
48
|
+
/** Column to sort by (whitelisted). Defaults to `at`. */
|
|
49
|
+
sort;
|
|
50
|
+
/** Sort direction. Defaults to `desc`. */
|
|
51
|
+
order;
|
|
52
|
+
}
|
|
53
|
+
exports.ListActivityQueryDto = ListActivityQueryDto;
|
|
54
|
+
tslib_1.__decorate([
|
|
55
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
56
|
+
type: String,
|
|
57
|
+
maxLength: 255,
|
|
58
|
+
example: 'user',
|
|
59
|
+
description: "Restrict to one subject kind (e.g. `'user'`)."
|
|
60
|
+
}),
|
|
61
|
+
(0, class_validator_1.IsOptional)(),
|
|
62
|
+
(0, class_validator_1.IsString)(),
|
|
63
|
+
(0, class_validator_1.MaxLength)(255),
|
|
64
|
+
tslib_1.__metadata("design:type", String)
|
|
65
|
+
], ListActivityQueryDto.prototype, "subjectType", void 0);
|
|
66
|
+
tslib_1.__decorate([
|
|
67
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
68
|
+
type: String,
|
|
69
|
+
maxLength: 255,
|
|
70
|
+
description: "Restrict to one subject id (a specific entity's history). Text, not always a uuid — the audit outlives its subjects."
|
|
71
|
+
}),
|
|
72
|
+
(0, class_validator_1.IsOptional)(),
|
|
73
|
+
(0, class_validator_1.IsString)(),
|
|
74
|
+
(0, class_validator_1.MaxLength)(255),
|
|
75
|
+
tslib_1.__metadata("design:type", String)
|
|
76
|
+
], ListActivityQueryDto.prototype, "subjectId", void 0);
|
|
77
|
+
tslib_1.__decorate([
|
|
78
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
79
|
+
type: String,
|
|
80
|
+
format: 'uuid',
|
|
81
|
+
description: 'Restrict to the events recorded for one actor.'
|
|
82
|
+
}),
|
|
83
|
+
(0, class_validator_1.IsOptional)(),
|
|
84
|
+
(0, class_validator_1.IsUUID)(),
|
|
85
|
+
tslib_1.__metadata("design:type", String)
|
|
86
|
+
], ListActivityQueryDto.prototype, "actorId", void 0);
|
|
87
|
+
tslib_1.__decorate([
|
|
88
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
89
|
+
type: String,
|
|
90
|
+
example: 'user.invited,user.suspended',
|
|
91
|
+
description: 'Comma-separated event kinds, matched with `IN`. Kinds are owned by the emitting plugins, so this is free text rather than a central catalogue.'
|
|
92
|
+
}),
|
|
93
|
+
(0, class_validator_1.IsOptional)(),
|
|
94
|
+
(0, class_transformer_1.Transform)(({ value }) => typeof value === 'string'
|
|
95
|
+
? value
|
|
96
|
+
.split(',')
|
|
97
|
+
.map((part) => part.trim())
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
: value),
|
|
100
|
+
(0, class_validator_1.IsArray)(),
|
|
101
|
+
(0, class_validator_1.IsString)({ each: true }),
|
|
102
|
+
(0, class_validator_1.MaxLength)(255, { each: true }),
|
|
103
|
+
tslib_1.__metadata("design:type", Array)
|
|
104
|
+
], ListActivityQueryDto.prototype, "kind", void 0);
|
|
105
|
+
tslib_1.__decorate([
|
|
106
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
107
|
+
type: String,
|
|
108
|
+
maxLength: 255,
|
|
109
|
+
description: 'Case-insensitive substring search over the frozen actor-email snapshot.'
|
|
110
|
+
}),
|
|
111
|
+
(0, class_validator_1.IsOptional)(),
|
|
112
|
+
(0, class_validator_1.IsString)(),
|
|
113
|
+
(0, class_validator_1.MaxLength)(255),
|
|
114
|
+
tslib_1.__metadata("design:type", String)
|
|
115
|
+
], ListActivityQueryDto.prototype, "actorEmail", void 0);
|
|
116
|
+
tslib_1.__decorate([
|
|
117
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
118
|
+
type: String,
|
|
119
|
+
maxLength: activity_constants_1.FILTER_MAX_LENGTH,
|
|
120
|
+
example: '{"op":"and","rules":[{"field":"kind","op":"eq","value":"user.invited"}]}',
|
|
121
|
+
description: 'Structured filter tree as a JSON string, produced by the admin query builder and validated against the activity filter schema. AND-ed with the other params.'
|
|
122
|
+
}),
|
|
123
|
+
(0, class_validator_1.IsOptional)(),
|
|
124
|
+
(0, class_validator_1.IsString)(),
|
|
125
|
+
(0, class_validator_1.MaxLength)(activity_constants_1.FILTER_MAX_LENGTH),
|
|
126
|
+
tslib_1.__metadata("design:type", String)
|
|
127
|
+
], ListActivityQueryDto.prototype, "filter", void 0);
|
|
128
|
+
tslib_1.__decorate([
|
|
129
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
130
|
+
type: String,
|
|
131
|
+
format: 'date-time',
|
|
132
|
+
example: '2026-01-01T00:00:00.000Z',
|
|
133
|
+
description: 'Inclusive lower bound on the event time (ISO 8601).'
|
|
134
|
+
}),
|
|
135
|
+
(0, class_validator_1.IsOptional)(),
|
|
136
|
+
(0, class_validator_1.IsISO8601)(),
|
|
137
|
+
tslib_1.__metadata("design:type", String)
|
|
138
|
+
], ListActivityQueryDto.prototype, "from", void 0);
|
|
139
|
+
tslib_1.__decorate([
|
|
140
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
141
|
+
type: String,
|
|
142
|
+
format: 'date-time',
|
|
143
|
+
example: '2026-02-01T00:00:00.000Z',
|
|
144
|
+
description: 'Inclusive upper bound on the event time (ISO 8601).'
|
|
145
|
+
}),
|
|
146
|
+
(0, class_validator_1.IsOptional)(),
|
|
147
|
+
(0, class_validator_1.IsISO8601)(),
|
|
148
|
+
tslib_1.__metadata("design:type", String)
|
|
149
|
+
], ListActivityQueryDto.prototype, "to", void 0);
|
|
150
|
+
tslib_1.__decorate([
|
|
151
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
152
|
+
type: 'integer',
|
|
153
|
+
minimum: 1,
|
|
154
|
+
default: 1,
|
|
155
|
+
description: '1-based page number.'
|
|
156
|
+
}),
|
|
157
|
+
(0, class_validator_1.IsOptional)(),
|
|
158
|
+
(0, class_transformer_1.Type)(() => Number),
|
|
159
|
+
(0, class_validator_1.IsInt)(),
|
|
160
|
+
(0, class_validator_1.Min)(1),
|
|
161
|
+
tslib_1.__metadata("design:type", Number)
|
|
162
|
+
], ListActivityQueryDto.prototype, "page", void 0);
|
|
163
|
+
tslib_1.__decorate([
|
|
164
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
165
|
+
type: 'integer',
|
|
166
|
+
minimum: 1,
|
|
167
|
+
maximum: activity_constants_1.MAX_PAGE_SIZE,
|
|
168
|
+
default: activity_constants_1.DEFAULT_PAGE_SIZE,
|
|
169
|
+
description: `Rows per page, capped at ${activity_constants_1.MAX_PAGE_SIZE}.`
|
|
170
|
+
}),
|
|
171
|
+
(0, class_validator_1.IsOptional)(),
|
|
172
|
+
(0, class_transformer_1.Type)(() => Number),
|
|
173
|
+
(0, class_validator_1.IsInt)(),
|
|
174
|
+
(0, class_validator_1.Min)(1),
|
|
175
|
+
(0, class_validator_1.Max)(activity_constants_1.MAX_PAGE_SIZE),
|
|
176
|
+
tslib_1.__metadata("design:type", Number)
|
|
177
|
+
], ListActivityQueryDto.prototype, "pageSize", void 0);
|
|
178
|
+
tslib_1.__decorate([
|
|
179
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
180
|
+
enum: [...activity_constants_1.SORTABLE_FIELDS],
|
|
181
|
+
default: 'at',
|
|
182
|
+
description: 'Column to sort by (whitelisted).'
|
|
183
|
+
}),
|
|
184
|
+
(0, class_validator_1.IsOptional)(),
|
|
185
|
+
(0, class_validator_1.IsIn)(activity_constants_1.SORTABLE_FIELDS),
|
|
186
|
+
tslib_1.__metadata("design:type", String)
|
|
187
|
+
], ListActivityQueryDto.prototype, "sort", void 0);
|
|
188
|
+
tslib_1.__decorate([
|
|
189
|
+
(0, swagger_1.ApiPropertyOptional)({
|
|
190
|
+
enum: [...activity_constants_1.SORT_ORDERS],
|
|
191
|
+
default: 'desc',
|
|
192
|
+
description: 'Sort direction.'
|
|
193
|
+
}),
|
|
194
|
+
(0, class_validator_1.IsOptional)(),
|
|
195
|
+
(0, class_validator_1.IsIn)(activity_constants_1.SORT_ORDERS),
|
|
196
|
+
tslib_1.__metadata("design:type", String)
|
|
197
|
+
], ListActivityQueryDto.prototype, "order", void 0);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { DomainEvent } from '@orthacms/database';
|
|
2
|
+
/**
|
|
3
|
+
* The values one audit row needs, derived purely from a {@link DomainEvent}.
|
|
4
|
+
* Mirrors an `activity_events` insert: `id` is the **event's** id (so a
|
|
5
|
+
* re-delivered event maps to the same row — the subscriber inserts it
|
|
6
|
+
* `ON CONFLICT DO NOTHING` for idempotency), and `at` is the event's domain
|
|
7
|
+
* time.
|
|
8
|
+
*/
|
|
9
|
+
export interface AuditRow {
|
|
10
|
+
/** Primary key = the source event id, making redelivery idempotent. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** The `user.*` / `workspace.*` audit kind (see the mapping table below). */
|
|
13
|
+
kind: string;
|
|
14
|
+
/** The kind of entity acted upon (`'user'` / `'workspace'`). */
|
|
15
|
+
subjectType: string;
|
|
16
|
+
/** The acted-upon entity's id (text). */
|
|
17
|
+
subjectId: string;
|
|
18
|
+
/** Who performed it, or `null` for a system-initiated event. */
|
|
19
|
+
actorId: string | null;
|
|
20
|
+
/** Frozen actor email snapshot, or `null`. */
|
|
21
|
+
actorEmail: string | null;
|
|
22
|
+
/** Open per-kind payload, or `null` when the kind carries none. */
|
|
23
|
+
meta: Record<string, unknown> | null;
|
|
24
|
+
/** Logical event time (the event's `occurredAt`). */
|
|
25
|
+
at: Date;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Thrown when an event cannot be mapped to a row that identifies its subject.
|
|
29
|
+
*
|
|
30
|
+
* Refusing is the point. `subject_id` is the **only** handle an audit row keeps
|
|
31
|
+
* on the entity it is about — there is no FK, no denormalised name, and
|
|
32
|
+
* `actor_email` belongs to the actor — so a row written with an empty subject
|
|
33
|
+
* is not a degraded record, it is an unreadable one, and nothing downstream can
|
|
34
|
+
* repair it. Throwing leaves the outbox row undispatched, so the dispatcher
|
|
35
|
+
* retries it with backoff and parks it at `MAX_DELIVERY_ATTEMPTS` where the
|
|
36
|
+
* dead-letter query (`dispatched_at IS NULL AND attempts >= 15`) surfaces it.
|
|
37
|
+
* The gap becomes loud instead of becoming a row nobody can read.
|
|
38
|
+
*/
|
|
39
|
+
export declare class UnmappableAuditEventError extends Error {
|
|
40
|
+
constructor(event: DomainEvent, reason: string);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Every event kind the activity subscriber audits — the dispatcher delivers
|
|
44
|
+
* only these to it.
|
|
45
|
+
*/
|
|
46
|
+
export declare const AUDITED_EVENT_KINDS: readonly string[];
|
|
47
|
+
/**
|
|
48
|
+
* Maps a domain event to the audit row it should produce, or `null` when the
|
|
49
|
+
* kind is not audited. Pure and DB-free — the safety-net unit test asserts each
|
|
50
|
+
* kind's row equals what the old in-band recorder wrote.
|
|
51
|
+
*
|
|
52
|
+
* Throws {@link UnmappableAuditEventError} when the kind **is** audited but the
|
|
53
|
+
* payload cannot identify its subject. `null` and a throw mean different things
|
|
54
|
+
* on purpose: `null` is "not our event, skip it" and the dispatcher marks the
|
|
55
|
+
* row delivered; a throw is "this should have been audited and cannot be", so
|
|
56
|
+
* the row stays undispatched, is retried with backoff, and finally parks as a
|
|
57
|
+
* dead letter rather than becoming a row nobody can read.
|
|
58
|
+
*/
|
|
59
|
+
export declare function toAuditRow(event: DomainEvent): AuditRow | null;
|
|
60
|
+
//# sourceMappingURL=audit-event-mapping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-event-mapping.d.ts","sourceRoot":"","sources":["../../../../src/lib/activity/infrastructure/audit-event-mapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACrB,uEAAuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACrC,qDAAqD;IACrD,EAAE,EAAE,IAAI,CAAC;CACZ;AAgKD;;;;;;;;;;;GAWG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;gBACpC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM;CAMjD;AA+OD;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAE3B,SAAS,MAAM,EAAE,CAAC;AAgBvB;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,QAAQ,GAAG,IAAI,CAiB9D"}
|