-
Notifications
You must be signed in to change notification settings - Fork 1
Migrate all plugin data storage to ObjectQL #250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
793be8b
a9cad97
488b05c
24d8b82
c058d5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| name: audit_log | ||
| label: Audit Log | ||
| icon: history | ||
| hidden: false | ||
| fields: | ||
| event_type: | ||
| type: select | ||
| label: Event Type | ||
| required: true | ||
| options: | ||
| - label: Create | ||
| value: create | ||
| - label: Update | ||
| value: update | ||
| - label: Delete | ||
| value: delete | ||
| - label: Read | ||
| value: read | ||
| - label: Login | ||
| value: login | ||
| - label: Logout | ||
| value: logout | ||
| - label: Permission Change | ||
| value: permission_change | ||
| - label: Custom | ||
| value: custom | ||
| index: true | ||
| object_name: | ||
| type: text | ||
| label: Object Name | ||
| index: true | ||
| record_id: | ||
| type: text | ||
| label: Record ID | ||
| index: true | ||
| user_id: | ||
| type: text | ||
| label: User ID | ||
| index: true | ||
| timestamp: | ||
| type: datetime | ||
| label: Timestamp | ||
| required: true | ||
| index: true | ||
| ip_address: | ||
| type: text | ||
| label: IP Address | ||
| user_agent: | ||
| type: text | ||
| label: User Agent | ||
| session_id: | ||
| type: text | ||
| label: Session ID | ||
| index: true | ||
| changes: | ||
| type: object | ||
| label: Field Changes | ||
| blackbox: true | ||
| metadata: | ||
| type: object | ||
| label: Additional Metadata | ||
| blackbox: true |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,182 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * ObjectQL Audit Storage Implementation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Storage adapter that persists audit events to ObjectOS/ObjectQL database | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { PluginContext } from '@objectstack/runtime'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AuditStorage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AuditLogEntry, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AuditQueryOptions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FieldChange, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AuditTrailEntry, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from './types.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class ObjectQLAuditStorage implements AuditStorage { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private context: PluginContext; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor(context: PluginContext) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.context = context; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Store an audit event | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async logEvent(entry: AuditLogEntry): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await (this.context as any).broker.call('data.create', { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| object: 'audit_log', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| doc: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event_type: entry.eventType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| object_name: (entry as any).objectName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| record_id: (entry as any).recordId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_id: entry.userId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timestamp: entry.timestamp || new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ip_address: entry.ipAddress, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_agent: entry.userAgent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| session_id: entry.sessionId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| changes: (entry as any).changes, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+23
to
+38
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | |
| * Store an audit event | |
| */ | |
| async logEvent(entry: AuditLogEntry): Promise<void> { | |
| await (this.context as any).broker.call('data.create', { | |
| object: 'audit_log', | |
| doc: { | |
| event_type: entry.eventType, | |
| object_name: (entry as any).objectName, | |
| record_id: (entry as any).recordId, | |
| user_id: entry.userId, | |
| timestamp: entry.timestamp || new Date().toISOString(), | |
| ip_address: entry.ipAddress, | |
| user_agent: entry.userAgent, | |
| session_id: entry.sessionId, | |
| changes: (entry as any).changes, | |
| private isAuditTrailEntry(entry: AuditLogEntry | AuditTrailEntry): entry is AuditTrailEntry { | |
| return ( | |
| typeof (entry as AuditTrailEntry).objectName === 'string' && | |
| typeof (entry as AuditTrailEntry).recordId === 'string' | |
| ); | |
| } | |
| /** | |
| * Store an audit event | |
| */ | |
| async logEvent(entry: AuditLogEntry | AuditTrailEntry): Promise<void> { | |
| const isTrail = this.isAuditTrailEntry(entry); | |
| const objectName = isTrail ? entry.objectName : undefined; | |
| const recordId = isTrail ? entry.recordId : undefined; | |
| const changes = isTrail ? entry.changes : undefined; | |
| await (this.context as any).broker.call('data.create', { | |
| object: 'audit_log', | |
| doc: { | |
| event_type: entry.eventType, | |
| object_name: objectName, | |
| record_id: recordId, | |
| user_id: entry.userId, | |
| timestamp: entry.timestamp || new Date().toISOString(), | |
| ip_address: entry.ipAddress, | |
| user_agent: entry.userAgent, | |
| session_id: entry.sessionId, | |
| changes: changes, |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new ObjectQLAuditStorage class has no test coverage. Existing storage tests only cover InMemoryAuditStorage. The ObjectQL storage adapter should have tests that verify: 1) proper mapping between AuditLogEntry/AuditTrailEntry and database documents, 2) query filtering and pagination, 3) field history extraction, 4) error handling for broker call failures.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ import type { | |
| PluginStartupResult, | ||
| } from './types.js'; | ||
| import { InMemoryAuditStorage } from './storage.js'; | ||
| import { ObjectQLAuditStorage } from './objectql-storage.js'; | ||
|
|
||
| /** | ||
| * Audit Log Plugin | ||
|
|
@@ -61,6 +62,13 @@ export class AuditLogPlugin implements Plugin { | |
| this.context = context; | ||
| this.startedAt = Date.now(); | ||
|
|
||
| // Upgrade storage to ObjectQL if not explicitly provided and broker is available | ||
| // We do this in init because we need the context | ||
| if (!this.config.storage && (context as any).broker) { | ||
|
||
| this.storage = new ObjectQLAuditStorage(context); | ||
| context.logger.info('[Audit Log] Upgraded to ObjectQL storage'); | ||
| } | ||
|
|
||
| // Register audit log service | ||
| context.registerService('audit-log', this); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| name: formula_field | ||
| label: Formula Field | ||
| icon: calculator | ||
| hidden: true | ||
| fields: | ||
| object_name: | ||
| type: text | ||
| required: true | ||
| label: Object Name | ||
| index: true | ||
| name: | ||
| type: text | ||
| required: true | ||
| label: Field Name | ||
| formula: | ||
| type: object | ||
| label: Formula Definition | ||
| blackbox: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The broker property is accessed via type casting
(this.context as any).broker, which bypasses TypeScript's type safety. This same issue appears throughout all ObjectQL storage implementations. Consider adding proper typing for the broker property in PluginContext or creating a typed wrapper method.