Skip to content

Commit

Permalink
Added activity logger class
Browse files Browse the repository at this point in the history
  • Loading branch information
holoyan committed Jun 22, 2024
1 parent 6ac9d26 commit fc99fee
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 5 deletions.
5 changes: 0 additions & 5 deletions providers/README.md

This file was deleted.

25 changes: 25 additions & 0 deletions providers/activity_log_provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ApplicationService } from '@adonisjs/core/types'
import MorphMap from '../src/morph_map.js'
import { LogManager } from '../src/logger.js'
import ActivityLog from '../src/models/activity_log.js'

declare module '@adonisjs/core/types' {
export interface ContainerBindings {
morphMap: MorphMap
}
}
export default class ActivityLogProvider {
constructor(protected app: ApplicationService) {}

register() {
this.app.container.singleton('morphMap', async () => {
return new MorphMap()
})
}

async boot() {
LogManager.setModelClass(ActivityLog)
const map = await this.app.container.make('morphMap')
LogManager.setMorphMap(map)
}
}
23 changes: 23 additions & 0 deletions src/decorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import app from '@adonisjs/core/services/app'

export function MorphMap(param: string) {
return function <T extends { new (...args: any[]): {} }>(target: T) {
const service = async function () {
var result = await app.container.make('morphMap')
result.set(param, target)
return param
}

target.prototype.__morphMapName = service()
target.prototype.__morphMapName = param
}
}

export function getClassPath<T extends { new (...args: any[]): {} }>(clazz: T): string {
const morphMapName = clazz.prototype.__morphMapName
if (!morphMapName) {
throw new Error('morph map name not specified')
}

return morphMapName
}
148 changes: 148 additions & 0 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { BaseModel } from '@adonisjs/lucid/orm'
import {
LucidModel,
ModelAdapterOptions,
ModelQueryBuilderContract,
} from '@adonisjs/lucid/types/model'
import { LogModel, MorphInterface } from './types.js'

export class LogManager {
static _modelClass: typeof BaseModel

private static _map: MorphInterface

static setModelClass(modelClass: typeof BaseModel) {
this._modelClass = modelClass
}

static setMorphMap(map: MorphInterface) {
this._map = map
}

static morphMap() {
return this._map
}
}

export class ActivityBuilder {
private _adapterOptions?: ModelAdapterOptions
private _queryBuilder: ModelQueryBuilderContract<LucidModel, LucidModel> | null = null

queryOptions(options?: ModelAdapterOptions) {
this._adapterOptions = options
return this
}

private _name: string | null = null
private _description: string | null = null

private _modelId: string | number | null = null
private _modelType: string | null = null

private _event: string | null = null

private _entityId: string | number | null = null
private _entityType: string | null = null

private _properties: Object | null = null
private _batchId: string | null = null

name(name: string) {
this._name = name
return this
}

by(model: string, modelId: string | number): ActivityBuilder
by(model: LogModel): ActivityBuilder
by(model: LogModel | string, modelId?: string | number) {
if (typeof model !== 'string') {
this._modelId = model.getModelId()
this._modelType = LogManager.morphMap().getAlias(model)
} else if (typeof modelId === 'string' || typeof modelId === 'number') {
this._modelId = modelId
this._modelType = model
} else {
throw new Error('Invalid arguments provided')
}
return this
}

making(event: string) {
this._event = event
return this
}

on(entity: string, entityId: string | number): ActivityBuilder
on(entity: LogModel): ActivityBuilder
on(entity: LogModel | string, entityId?: string | number) {
if (typeof entity !== 'string') {
this._entityId = entity.getModelId()
this._entityType = LogManager.morphMap().getAlias(entity)
} else if (typeof entityId === 'string' || typeof entityId === 'number') {
this._entityId = entityId
this._entityType = entity
} else {
throw new Error('Invalid arguments provided')
}
return this
}

havingProperties(state: Object) {
this._properties = state
return this
}

withBatch(batchId: string) {
this._batchId = batchId
return this
}

log(message: string) {
const state = this.state()
state.description = message
// Here you would typically save the log to the database or perform the logging operation
console.log(state)
}

state() {
return {
name: this._name,
model_id: this._modelId,
model_type: this._modelType,
event: this._event,
entity_id: this._entityId,
entity_type: this._entityType,
properties: this._properties,
batch_id: this._batchId,
description: this._description,
}
}

customQuery(callback: (query: ModelQueryBuilderContract<LucidModel, LucidModel>) => void) {
callback(this.getBuilder())
return this
}

private getBuilder() {
if (!this._queryBuilder) {
this._queryBuilder = LogManager._modelClass.query(this._adapterOptions)
}
return this._queryBuilder
}
}

export function activity() {
return new ActivityBuilder()
}

// Example usage:
activity()
.by('User', 1)
.making('edit')
.on('Product', 2)
.havingProperties({})
.customQuery((query) => {
query.where('status', 'active')
query.where('created_at', '>=', new Date('2023-01-01'))
})
.log('Edited product')
47 changes: 47 additions & 0 deletions src/models/activity_log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { DateTime } from 'luxon'
import { BaseModel, column, scope } from '@adonisjs/lucid/orm'

export default class ActivityLog extends BaseModel {
@column({ isPrimary: true })
declare id: number

@column()
declare name: string

@column()
declare description: string

@column()
declare model_type: string

@column()
declare model_id: string

@column()
declare event: string

@column()
declare entity_type: string

@column()
declare entity_id: string

@column()
declare properties: Object

@column()
declare batch_id: string

@column.dateTime({ autoCreate: true })
declare createdAt: DateTime

@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime

static published = scope((query) => {
query.where('publishedOn', '<=', DateTime.utc().toSQLDate())
})
}

const a = ActivityLog.query()
a.where('a', 4).withScopes((scopes) => scopes.published())
53 changes: 53 additions & 0 deletions src/morph_map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { MorphInterface, MorphMapInterface } from './types.js'

export default class MorphMap implements MorphInterface {
private _map: MorphMapInterface = {}

private static _instance?: MorphMap

static create() {
if (this._instance) {
return this._instance
}

return new MorphMap()
}

set(alias: string, target: any) {
this._map[alias] = target
}

get(alias: string) {
if (!(alias in this._map)) {
throw new Error('morph map not found for ' + alias)
}

return this._map[alias] || null
}

has(alias: string) {
return alias in this._map
}

hasTarget(target: any) {
const keys = Object.keys(this._map)
for (const key of keys) {
if (this._map[key] === target) {
return true
}
}

return false
}

getAlias(target: any) {
const keys = Object.keys(this._map)
for (const key of keys) {
if (target instanceof this._map[key] || target === this._map[key]) {
return key
}
}

throw new Error('Target not found')
}
}
20 changes: 20 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { LucidModel } from '@adonisjs/lucid/types/model'

export interface LogModelInterface {
getModelId(): string | number
}

// export type LogModel = InstanceType<LucidModel> & LogModelInterface
export interface LogModel extends LucidModel, LogModelInterface {}

export interface MorphMapInterface {
[key: string]: any
}

export interface MorphInterface {
set(alias: string, target: any): void
get(alias: string): any
has(alias: string): boolean
hasTarget(target: any): boolean
getAlias(target: any): string
}
36 changes: 36 additions & 0 deletions stubs/migrations/create_db.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{{{
exports({
to: app.makePath('database', 'migrations', prefix + '_create_role_permissions_table.ts')
})
}}}
import { BaseSchema } from '@adonisjs/lucid/schema'

export default class extends BaseSchema {
protected tableName = 'activity_logs'

async up() {
this.schema.createTable(this.tableName, (table) => {
table.increments('id')

table.string('name').nullable().index()
table.text('description')
table.string('model_type').nullable()
table.string('model_id').nullable()
table.string('event').nullable()
table.string('entity_type').nullable()
table.string('entity_id').nullable()
table.json('properties').nullable()
table.string('batch_id').nullable().index()

table.index(['model_type', 'model_id'])
table.index(['entity_type', 'entity_id'])

table.timestamp('created_at')
table.timestamp('updated_at')
})
}

async down() {
this.schema.dropTable(this.tableName)
}
}

0 comments on commit fc99fee

Please sign in to comment.