@mostajs/audit 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Dr Hamid MADANI <drmdh@msn.com>
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
@@ -0,0 +1,71 @@
1
+ # @mostajs/audit
2
+
3
+ > Reusable audit logging module — fire-and-forget `logAudit()` with paginated consultation.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@mostajs/audit.svg)](https://www.npmjs.com/package/@mostajs/audit)
6
+ [![license](https://img.shields.io/npm/l/@mostajs/audit.svg)](LICENSE)
7
+
8
+ Part of the [@mosta suite](https://mostajs.dev).
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @mostajs/audit @mostajs/orm
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ### 1. Register the schema
21
+
22
+ ```typescript
23
+ import { registerSchema } from '@mostajs/orm'
24
+ import { AuditLogSchema } from '@mostajs/audit'
25
+
26
+ registerSchema(AuditLogSchema)
27
+ ```
28
+
29
+ ### 2. Log an audit event
30
+
31
+ ```typescript
32
+ import { logAudit } from '@mostajs/audit'
33
+
34
+ await logAudit({
35
+ userId: session.user.id,
36
+ userName: session.user.name,
37
+ userRole: 'admin',
38
+ action: 'create',
39
+ module: 'users',
40
+ resource: 'User',
41
+ resourceId: newUser.id,
42
+ })
43
+ ```
44
+
45
+ ### 3. Query audit logs (API route)
46
+
47
+ ```typescript
48
+ import { createAuditHandlers } from '@mostajs/audit'
49
+ import { checkPermission } from '@/lib/auth'
50
+
51
+ export const { GET } = createAuditHandlers('audit:view', checkPermission)
52
+ ```
53
+
54
+ ## API Reference
55
+
56
+ | Export | Description |
57
+ |--------|-------------|
58
+ | `logAudit(params)` | Fire-and-forget audit entry |
59
+ | `getAuditUser(session)` | Extract user info from NextAuth session |
60
+ | `AuditLogRepository` | Repository with `findPaginated()`, `findByResource()`, `deleteOlderThan()` |
61
+ | `AuditLogSchema` | Entity schema for registration |
62
+ | `createAuditHandlers()` | API route factory for paginated consultation |
63
+
64
+ ## Related Packages
65
+
66
+ - [@mostajs/orm](https://www.npmjs.com/package/@mostajs/orm) — Multi-dialect ORM (required)
67
+ - [@mostajs/auth](https://www.npmjs.com/package/@mostajs/auth) — Authentication & RBAC
68
+
69
+ ## License
70
+
71
+ MIT — © 2025 Dr Hamid MADANI <drmdh@msn.com>
@@ -0,0 +1,12 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ type PermissionChecker = (permission: string) => Promise<{
3
+ error: NextResponse | null;
4
+ session: any;
5
+ }>;
6
+ /**
7
+ * Creates a GET handler for paginated audit log consultation.
8
+ */
9
+ export declare function createAuditHandlers(permission: string, checkPermission: PermissionChecker): {
10
+ GET: (req: NextRequest) => Promise<NextResponse<unknown>>;
11
+ };
12
+ export {};
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ // @mosta/audit — API Route template
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ //
5
+ // Copy to: src/app/api/admin/audit/route.ts
6
+ // Usage:
7
+ // import { createAuditHandlers } from '@mosta/audit/api/route'
8
+ // import { checkPermission } from '@mosta/auth'
9
+ // export const { GET } = createAuditHandlers('audit:view', checkPermission)
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.createAuditHandlers = createAuditHandlers;
12
+ const server_1 = require("next/server");
13
+ const orm_1 = require("@mostajs/orm");
14
+ const audit_log_repository_1 = require("../repositories/audit-log.repository");
15
+ /**
16
+ * Creates a GET handler for paginated audit log consultation.
17
+ */
18
+ function createAuditHandlers(permission, checkPermission) {
19
+ async function GET(req) {
20
+ const { error } = await checkPermission(permission);
21
+ if (error)
22
+ return error;
23
+ const url = req.nextUrl;
24
+ const filters = {
25
+ module: url.searchParams.get('module') || undefined,
26
+ action: url.searchParams.get('action') || undefined,
27
+ userId: url.searchParams.get('userId') || undefined,
28
+ status: url.searchParams.get('status') || undefined,
29
+ from: url.searchParams.get('from') ? new Date(url.searchParams.get('from')) : undefined,
30
+ to: url.searchParams.get('to') ? new Date(url.searchParams.get('to')) : undefined,
31
+ page: parseInt(url.searchParams.get('page') || '1', 10),
32
+ limit: parseInt(url.searchParams.get('limit') || '50', 10),
33
+ };
34
+ const repo = new audit_log_repository_1.AuditLogRepository(await (0, orm_1.getDialect)());
35
+ const { data, total } = await repo.findPaginated(filters);
36
+ return server_1.NextResponse.json({
37
+ data,
38
+ meta: {
39
+ total,
40
+ page: filters.page,
41
+ limit: filters.limit,
42
+ pages: Math.ceil(total / filters.limit),
43
+ },
44
+ });
45
+ }
46
+ return { GET };
47
+ }
@@ -0,0 +1,5 @@
1
+ export { logAudit, getAuditUser } from './lib/audit';
2
+ export { AuditLogRepository } from './repositories/audit-log.repository';
3
+ export { AuditLogSchema } from './schemas/audit-log.schema';
4
+ export { createAuditHandlers } from './api/route';
5
+ export type { MostaAuditConfig, AuditParams, AuditFilters, AuditLogDTO, } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ // @mosta/audit — Barrel exports
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.createAuditHandlers = exports.AuditLogSchema = exports.AuditLogRepository = exports.getAuditUser = exports.logAudit = void 0;
6
+ // Core
7
+ var audit_1 = require("./lib/audit");
8
+ Object.defineProperty(exports, "logAudit", { enumerable: true, get: function () { return audit_1.logAudit; } });
9
+ Object.defineProperty(exports, "getAuditUser", { enumerable: true, get: function () { return audit_1.getAuditUser; } });
10
+ // Repository & Schema
11
+ var audit_log_repository_1 = require("./repositories/audit-log.repository");
12
+ Object.defineProperty(exports, "AuditLogRepository", { enumerable: true, get: function () { return audit_log_repository_1.AuditLogRepository; } });
13
+ var audit_log_schema_1 = require("./schemas/audit-log.schema");
14
+ Object.defineProperty(exports, "AuditLogSchema", { enumerable: true, get: function () { return audit_log_schema_1.AuditLogSchema; } });
15
+ // API helpers
16
+ var route_1 = require("./api/route");
17
+ Object.defineProperty(exports, "createAuditHandlers", { enumerable: true, get: function () { return route_1.createAuditHandlers; } });
@@ -0,0 +1,13 @@
1
+ import type { AuditParams } from '../types';
2
+ /**
3
+ * Log an audit entry. Fire-and-forget — never throws, never blocks.
4
+ */
5
+ export declare function logAudit(params: AuditParams): Promise<void>;
6
+ /**
7
+ * Extract audit user info from a NextAuth-style session.
8
+ */
9
+ export declare function getAuditUser(session: any): {
10
+ userId: string;
11
+ userName: string;
12
+ userRole: string;
13
+ };
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logAudit = logAudit;
4
+ exports.getAuditUser = getAuditUser;
5
+ // @mosta/audit — Core audit functions
6
+ // Author: Dr Hamid MADANI drmdh@msn.com
7
+ const orm_1 = require("@mostajs/orm");
8
+ const audit_log_repository_1 = require("../repositories/audit-log.repository");
9
+ /**
10
+ * Log an audit entry. Fire-and-forget — never throws, never blocks.
11
+ */
12
+ async function logAudit(params) {
13
+ try {
14
+ const repo = new audit_log_repository_1.AuditLogRepository(await (0, orm_1.getDialect)());
15
+ await repo.create({
16
+ userId: params.userId,
17
+ userName: params.userName,
18
+ userRole: params.userRole,
19
+ action: params.action,
20
+ module: params.module,
21
+ resource: params.resource || '',
22
+ resourceId: params.resourceId || '',
23
+ details: params.details || {},
24
+ ipAddress: params.ipAddress || '',
25
+ status: params.status || 'success',
26
+ timestamp: new Date().toISOString(),
27
+ });
28
+ }
29
+ catch (err) {
30
+ console.error('[MostaAudit] Error:', err);
31
+ }
32
+ }
33
+ /**
34
+ * Extract audit user info from a NextAuth-style session.
35
+ */
36
+ function getAuditUser(session) {
37
+ const { role, roles } = session.user;
38
+ return {
39
+ userId: session.user.id,
40
+ userName: session.user.name || session.user.email,
41
+ userRole: role || (Array.isArray(roles) ? roles.join(', ') : 'unknown'),
42
+ };
43
+ }
@@ -0,0 +1,15 @@
1
+ import { BaseRepository } from '@mostajs/orm';
2
+ import type { IDialect, QueryOptions } from '@mostajs/orm';
3
+ import type { AuditLogDTO, AuditFilters } from '../types';
4
+ export declare class AuditLogRepository extends BaseRepository<AuditLogDTO> {
5
+ constructor(dialect: IDialect);
6
+ /** Find paginated audit logs with optional filters */
7
+ findPaginated(filters?: AuditFilters, options?: QueryOptions): Promise<{
8
+ data: AuditLogDTO[];
9
+ total: number;
10
+ }>;
11
+ /** Find audit logs related to a specific resource */
12
+ findByResource(resourceId: string, modules?: string[], options?: QueryOptions): Promise<AuditLogDTO[]>;
13
+ /** Delete logs older than N days */
14
+ deleteOlderThan(days: number): Promise<number>;
15
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuditLogRepository = void 0;
4
+ // @mosta/audit — AuditLogRepository
5
+ // Author: Dr Hamid MADANI drmdh@msn.com
6
+ const orm_1 = require("@mostajs/orm");
7
+ const audit_log_schema_1 = require("../schemas/audit-log.schema");
8
+ class AuditLogRepository extends orm_1.BaseRepository {
9
+ constructor(dialect) {
10
+ super(audit_log_schema_1.AuditLogSchema, dialect);
11
+ }
12
+ /** Find paginated audit logs with optional filters */
13
+ async findPaginated(filters = {}, options) {
14
+ const query = {};
15
+ if (filters.module)
16
+ query.module = filters.module;
17
+ if (filters.action)
18
+ query.action = { $regex: filters.action, $regexFlags: 'i' };
19
+ if (filters.userId)
20
+ query.userId = filters.userId;
21
+ if (filters.status)
22
+ query.status = filters.status;
23
+ if (filters.from || filters.to) {
24
+ query.timestamp = {};
25
+ if (filters.from)
26
+ query.timestamp.$gte = filters.from;
27
+ if (filters.to)
28
+ query.timestamp.$lte = filters.to;
29
+ }
30
+ const page = filters.page || 1;
31
+ const limit = filters.limit || 50;
32
+ const skip = (page - 1) * limit;
33
+ const [data, total] = await Promise.all([
34
+ this.findAll(query, { sort: { timestamp: -1 }, skip, limit, ...options }),
35
+ this.count(query),
36
+ ]);
37
+ return { data, total };
38
+ }
39
+ /** Find audit logs related to a specific resource */
40
+ async findByResource(resourceId, modules, options) {
41
+ const query = { resourceId };
42
+ if (modules && modules.length > 0) {
43
+ query.module = { $in: modules };
44
+ }
45
+ return this.findAll(query, { sort: { timestamp: -1 }, ...options });
46
+ }
47
+ /** Delete logs older than N days */
48
+ async deleteOlderThan(days) {
49
+ const cutoff = new Date(Date.now() - days * 86400000);
50
+ return this.deleteMany({ timestamp: { $lt: cutoff } });
51
+ }
52
+ }
53
+ exports.AuditLogRepository = AuditLogRepository;
@@ -0,0 +1,2 @@
1
+ import type { EntitySchema } from '@mostajs/orm';
2
+ export declare const AuditLogSchema: EntitySchema;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuditLogSchema = void 0;
4
+ exports.AuditLogSchema = {
5
+ name: 'AuditLog',
6
+ collection: 'auditlogs',
7
+ timestamps: false,
8
+ fields: {
9
+ userName: { type: 'string', required: true },
10
+ userRole: { type: 'string', required: true },
11
+ action: { type: 'string', required: true },
12
+ module: { type: 'string', required: true },
13
+ resource: { type: 'string', default: '' },
14
+ resourceId: { type: 'string', default: '' },
15
+ details: { type: 'json' },
16
+ ipAddress: { type: 'string', default: '' },
17
+ status: { type: 'string', enum: ['success', 'failure'], default: 'success' },
18
+ timestamp: { type: 'date', default: 'now' },
19
+ },
20
+ relations: {
21
+ userId: { target: 'User', type: 'many-to-one', required: true },
22
+ },
23
+ indexes: [
24
+ { fields: { timestamp: 'desc' } },
25
+ { fields: { module: 'asc', timestamp: 'desc' } },
26
+ { fields: { userId: 'asc', timestamp: 'desc' } },
27
+ ],
28
+ };
@@ -0,0 +1,46 @@
1
+ export interface MostaAuditConfig {
2
+ /** Collection/table name (default: 'auditlogs') */
3
+ collection?: string;
4
+ /** Retention in days, 0 = unlimited (default: 0) */
5
+ retentionDays?: number;
6
+ /** Known modules for UI filters */
7
+ modules?: string[];
8
+ /** Known actions for UI filters */
9
+ actions?: string[];
10
+ }
11
+ export interface AuditParams {
12
+ userId: string;
13
+ userName: string;
14
+ userRole: string;
15
+ action: string;
16
+ module: string;
17
+ resource?: string;
18
+ resourceId?: string;
19
+ details?: Record<string, any>;
20
+ ipAddress?: string;
21
+ status?: 'success' | 'failure';
22
+ }
23
+ export interface AuditFilters {
24
+ module?: string;
25
+ action?: string;
26
+ userId?: string;
27
+ status?: 'success' | 'failure';
28
+ from?: Date;
29
+ to?: Date;
30
+ page?: number;
31
+ limit?: number;
32
+ }
33
+ export interface AuditLogDTO {
34
+ id: string;
35
+ userId: any;
36
+ userName: string;
37
+ userRole: string;
38
+ action: string;
39
+ module: string;
40
+ resource: string;
41
+ resourceId: string;
42
+ details: Record<string, unknown> | null;
43
+ ipAddress: string;
44
+ status: 'success' | 'failure';
45
+ timestamp: string;
46
+ }
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // @mosta/audit — Types
3
+ // Author: Dr Hamid MADANI drmdh@msn.com
4
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@mostajs/audit",
3
+ "version": "1.0.0",
4
+ "description": "Reusable audit logging module — fire-and-forget logAudit() with paginated consultation",
5
+ "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./api/route": {
17
+ "types": "./dist/api/route.d.ts",
18
+ "import": "./dist/api/route.js",
19
+ "require": "./dist/api/route.js",
20
+ "default": "./dist/api/route.js"
21
+ }
22
+ },
23
+ "files": ["dist", "LICENSE", "README.md"],
24
+ "keywords": ["audit", "audit-log", "logging", "nextjs", "mosta"],
25
+ "repository": { "type": "git", "url": "https://github.com/apolocine/mosta-audit" },
26
+ "homepage": "https://mostajs.dev/packages/audit",
27
+ "engines": { "node": ">=18.0.0" },
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "dependencies": {
33
+ "@mostajs/orm": "^1.0.0"
34
+ },
35
+ "peerDependencies": {
36
+ "next": ">=14",
37
+ "react": ">=18"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "next": { "optional": true },
41
+ "react": { "optional": true }
42
+ },
43
+ "devDependencies": {
44
+ "@types/react": "^19.0.0",
45
+ "next": "^15.0.0",
46
+ "react": "^19.0.0",
47
+ "typescript": "^5.6.0"
48
+ }
49
+ }