@autofleet/shtinker 3.1.0 → 3.2.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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,`__esModule`,{value:!0});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`@autofleet/zehut`)),l=s(require(`lodash`)),u=s(require(`@autofleet/logger`)),d=s(require(`@autofleet/errors`)),f=s(require(`@autofleet/network`));let p=/* @__PURE__ */ function(e){return e.CREATE=`create`,e.BULK_CREATE=`bulk-create`,e.BULK_EDIT=`bulk-edit`,e.DELETE=`delete`,e.UPDATE=`update`,e.CANCEL=`cancel`,e.FAIL=`fail`,e.UNASSIGN=`unassign`,e.BULK_ASSIGN=`bulk-assign`,e.REASSIGN=`reassign`,e.DISPATCH=`dispatch`,e.BULK_DISPATCH=`bulk-dispatch`,e.BULK_UPSERT=`bulk-upsert`,e.UPSERT=`upsert`,e.JOIN=`join`,e.MOVE=`move`,e.REOPTIMIZATION=`reoptimization`,e}({}),m=/* @__PURE__ */ function(e){return e.RIDE=`Ride`,e.VEHICLE=`Vehicle`,e.DRIVER=`Driver`,e.PRICE_CALCULATION=`PriceCalculation`,e}({}),h=/* @__PURE__ */ function(e){return e.USER=`user`,e.AUTOMATION=`automation`,e}({});const g=`audit-log-context`,_=`audit-log-rows`,v=`auditLogContext`,y=`x-af-automation-id`,b=(0,u.default)(null);var x=b;const S=`customFields`,C=()=>{let e=(0,c.getCurrentPayload)(),t=e?.nonHeaderContext?.get(v);return t},w={[p.BULK_CREATE]:p.BULK_CREATE,[p.BULK_EDIT]:p.BULK_EDIT,[p.BULK_ASSIGN]:p.BULK_ASSIGN,[p.BULK_DISPATCH]:p.BULK_DISPATCH,[p.BULK_UPSERT]:p.BULK_UPSERT},T=e=>[null,void 0].includes(e)?!0:Array.isArray(e)?e.length===0:typeof e==`object`&&!(e instanceof Date)?Object.keys(e).length===0:!1,E=e=>!w[e],D=(e,t)=>e.filter(e=>!T(t.get(e))||!T(t.previous(e))),O=(e,t)=>{let n=t.returning?t.fields:e.changed(),r=D(n.filter(e=>e!==S),e);return r.map(t=>({property:t,previousValue:e.previous(t),newValue:e.get(t)}))},k=e=>{let t=e.changed(),n=t&&t.includes(S);if(!n)return[];let r=e.get(S),i=e.previous(S),a=Object.keys(r).filter(e=>{let t=r?.[e],n=i?.[e];return(!T(t)||!T(n))&&!(0,l.isEqual)(t,n)});return a.map(e=>({property:`${S}.${e}`,previousValue:i?.[e],newValue:r?.[e]}))};var A=class{constructor(e){this.rabbit=e.rabbit,this.sequelize=e.sequelize,this.logger=e.logger||x}registerHooks(){Object.entries(this.sequelize.models).forEach(([e,t])=>{t.addHook(`afterSave`,async(t,n)=>{try{let r=C();if(!r)return;r?.entityType?.toLowerCase()===e?.toLowerCase()&&E(r.action)&&(r.entityId=t.id);let i=O(t,n),a=k(t),o={entityType:e,entityId:t.id,rows:[...i,...a]};this.sendAuditLogRows(o)}catch(e){this.logger.error(`Failed to send audit log rows`,e)}})})}async sendAuditLogContext(e){try{await this.rabbit.sendToQueue(g,e)}catch(e){this.logger.error(`Failed to send audit log context`,e)}}async sendAuditLogRows(e){try{await this.rabbit.sendToQueue(_,e)}catch(e){this.logger.error(`Failed to send audit log rows`,e)}}},j=A;const M=new f.default({serviceName:`AUDIT_MS`,timeout:6e4,logger:x}),N=async e=>{let{data:t}=await M.get(`api/v1/audit-logs/${e}`);return t},P={getByEntityId:N};var F=P,I=async({router:e,logger:t,entityScopedModelMap:n})=>{!n||!e||Object.entries(n).forEach(([n,r])=>{e.get(`${n}/:id/audit`,async(e,i)=>{try{let{id:a}=e.params,o=await r.findByPk(a);if(!o)return(0,d.handleError)(new d.ResourceNotFoundError,i,{logger:t,message:`Entity ${n} with id ${a} not found`});let s=F.getByEntityId(a);return i.json(s)}catch(e){return(0,d.handleError)(new d.UnexpectedError(e),i,{logger:t})}})})};let L;const R=e=>{process.env.DISABLE_AUDIT_LOGS!==`true`&&(L=new j(e),I(e),L.registerHooks())},z=(e,t)=>async(n,r,i)=>{try{if(process.env.DISABLE_AUDIT_LOGS===`true`||!L)return i();let a=(0,c.getUser)(),o=n.headers[y],{performedBy:s,actionOrigin:l}=a?.id?{performedBy:a.id,actionOrigin:h.USER}:{performedBy:o??null,actionOrigin:o?h.AUTOMATION:null},u={entityType:e,action:t,endpoint:n.url,method:n.method,performedBy:s,actionOrigin:l},d=(0,c.getCurrentPayload)();d.nonHeaderContext.set(v,u);let f=async()=>{r.off(`finish`,f),r.off(`close`,f),r.off(`error`,f),await L.sendAuditLogContext(u)};E(t)?(r.once(`finish`,f),r.once(`close`,f),r.once(`error`,f)):await L.sendAuditLogContext(u)}catch(e){x.error(`coudln't set audit context`,e)}return i()},B=(e,t)=>async(n,{userId:r,automationId:i})=>{if(process.env.DISABLE_AUDIT_LOGS===`true`||!L)return;let a=(0,c.getCurrentPayload)();if(a?.context?.get){let{performedBy:o,actionOrigin:s}=r?{performedBy:r,actionOrigin:h.USER}:{performedBy:i??null,actionOrigin:i?h.AUTOMATION:null},c={entityType:e,action:t,endpoint:n,method:`rabbit`,performedBy:o,actionOrigin:s};a.nonHeaderContext.set(v,c),await L.sendAuditLogContext(c)}};var V=j;exports.AUDIT_LOG_CONTEXT_KEY=v,exports.AUDIT_LOG_CONTEXT_QUEUE=g,exports.AUDIT_LOG_ROWS_QUEUE=_,exports.AUTOMATION_ID_HEADER=y,exports.Action=p,exports.ActionOrigin=h,exports.EntityType=m,exports.default=V,exports.enableAuditing=R,exports.setAuditContext=z,exports.setRabbitAuditContext=B;
|
|
1
|
+
"use strict";Object.defineProperty(exports,`__esModule`,{value:!0});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`@autofleet/zehut`)),l=s(require(`lodash`)),u=s(require(`@autofleet/logger`)),d=s(require(`@autofleet/errors`)),f=s(require(`@autofleet/network`));let p=/* @__PURE__ */ function(e){return e.CREATE=`create`,e.BULK_CREATE=`bulk-create`,e.BULK_EDIT=`bulk-edit`,e.DELETE=`delete`,e.UPDATE=`update`,e.CANCEL=`cancel`,e.FAIL=`fail`,e.UNASSIGN=`unassign`,e.BULK_ASSIGN=`bulk-assign`,e.REASSIGN=`reassign`,e.DISPATCH=`dispatch`,e.BULK_DISPATCH=`bulk-dispatch`,e.BULK_UPSERT=`bulk-upsert`,e.UPSERT=`upsert`,e.JOIN=`join`,e.MOVE=`move`,e.REOPTIMIZATION=`reoptimization`,e}({}),m=/* @__PURE__ */ function(e){return e.RIDE=`Ride`,e.VEHICLE=`Vehicle`,e.DRIVER=`Driver`,e.PRICE_CALCULATION=`PriceCalculation`,e}({}),h=/* @__PURE__ */ function(e){return e.USER=`user`,e.AUTOMATION=`automation`,e}({});const g=`audit-log-context`,_=`audit-log-rows`,v=`auditLogContext`,y=`x-af-automation-id`,b=(0,u.default)(null);var x=b;const S=`customFields`,C=()=>{let e=(0,c.getCurrentPayload)(),t=e?.nonHeaderContext?.get(v);return t},w={[p.BULK_CREATE]:p.BULK_CREATE,[p.BULK_EDIT]:p.BULK_EDIT,[p.BULK_ASSIGN]:p.BULK_ASSIGN,[p.BULK_DISPATCH]:p.BULK_DISPATCH,[p.BULK_UPSERT]:p.BULK_UPSERT},T=e=>[null,void 0].includes(e)?!0:Array.isArray(e)?e.length===0:typeof e==`object`&&!(e instanceof Date)?Object.keys(e).length===0:!1,E=e=>!w[e],D=(e,t)=>e.filter(e=>!T(t.get(e))||!T(t.previous(e))),O=(e,t)=>{let n=t.returning?t.fields:e.changed(),r=D(n.filter(e=>e!==S),e);return r.map(t=>({property:t,previousValue:e.previous(t),newValue:e.get(t)}))},k=e=>{let t=e.changed(),n=t&&t.includes(S);if(!n)return[];let r=e.get(S),i=e.previous(S),a=Object.keys(r).filter(e=>{let t=r?.[e],n=i?.[e];return(!T(t)||!T(n))&&!(0,l.isEqual)(t,n)});return a.map(e=>({property:`${S}.${e}`,previousValue:i?.[e],newValue:r?.[e]}))};var A=class{constructor(e){this.rabbit=e.rabbit,this.sequelize=e.sequelize,this.logger=e.logger||x,this.excludeModels=e.excludeModels??[]}registerHooks(){Object.entries(this.sequelize.models).forEach(([e,t])=>{this.excludeModels.includes(e)||t.addHook(`afterSave`,async(t,n)=>{try{let r=C();if(!r)return;r?.entityType?.toLowerCase()===e?.toLowerCase()&&E(r.action)&&(r.entityId=t.id);let i=O(t,n),a=k(t),o={entityType:e,entityId:t.id,rows:[...i,...a]};this.sendAuditLogRows(o)}catch(e){this.logger.error(`Failed to send audit log rows`,e)}})})}async sendAuditLogContext(e){try{await this.rabbit.sendToQueue(g,e)}catch(e){this.logger.error(`Failed to send audit log context`,e)}}async sendAuditLogRows(e){try{await this.rabbit.sendToQueue(_,e)}catch(e){this.logger.error(`Failed to send audit log rows`,e)}}},j=A;const M=new f.default({serviceName:`AUDIT_MS`,timeout:6e4,logger:x}),N=async e=>{let{data:t}=await M.get(`api/v1/audit-logs/${e}`);return t},P={getByEntityId:N};var F=P,I=async({router:e,logger:t,entityScopedModelMap:n})=>{!n||!e||Object.entries(n).forEach(([n,r])=>{e.get(`${n}/:id/audit`,async(e,i)=>{try{let{id:a}=e.params,o=await r.findByPk(a);if(!o)return(0,d.handleError)(new d.ResourceNotFoundError,i,{logger:t,message:`Entity ${n} with id ${a} not found`});let s=F.getByEntityId(a);return i.json(s)}catch(e){return(0,d.handleError)(new d.UnexpectedError(e),i,{logger:t})}})})};let L;const R=e=>{process.env.DISABLE_AUDIT_LOGS!==`true`&&(L=new j(e),I(e),L.registerHooks())},z=(e,t)=>async(n,r,i)=>{try{if(process.env.DISABLE_AUDIT_LOGS===`true`||!L)return i();let a=(0,c.getUser)(),o=n.headers[y],{performedBy:s,actionOrigin:l}=a?.id?{performedBy:a.id,actionOrigin:h.USER}:{performedBy:o??null,actionOrigin:o?h.AUTOMATION:null},u={entityType:e,action:t,endpoint:n.url,method:n.method,performedBy:s,actionOrigin:l},d=(0,c.getCurrentPayload)();d.nonHeaderContext.set(v,u);let f=async()=>{r.off(`finish`,f),r.off(`close`,f),r.off(`error`,f),await L.sendAuditLogContext(u)};E(t)?(r.once(`finish`,f),r.once(`close`,f),r.once(`error`,f)):await L.sendAuditLogContext(u)}catch(e){x.error(`coudln't set audit context`,e)}return i()},B=(e,t)=>async(n,{userId:r,automationId:i})=>{if(process.env.DISABLE_AUDIT_LOGS===`true`||!L)return;let a=(0,c.getCurrentPayload)();if(a?.context?.get){let{performedBy:o,actionOrigin:s}=r?{performedBy:r,actionOrigin:h.USER}:{performedBy:i??null,actionOrigin:i?h.AUTOMATION:null},c={entityType:e,action:t,endpoint:n,method:`rabbit`,performedBy:o,actionOrigin:s};a.nonHeaderContext.set(v,c),await L.sendAuditLogContext(c)}};var V=j;exports.AUDIT_LOG_CONTEXT_KEY=v,exports.AUDIT_LOG_CONTEXT_QUEUE=g,exports.AUDIT_LOG_ROWS_QUEUE=_,exports.AUTOMATION_ID_HEADER=y,exports.Action=p,exports.ActionOrigin=h,exports.EntityType=m,exports.default=V,exports.enableAuditing=R,exports.setAuditContext=z,exports.setRabbitAuditContext=B;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["logger: LoggerInstanceManager","action: string","fields: string[]","instance: Model","options: CreateOptions<any> | InstanceUpdateOptions<any>","changedFields: string[]","property: string","options: AuditLoggerOptions","payload: AuditLogPayload","context: AuditLogContext","entityId: string","exportObj: { getByEntityId: typeof getByEntityId }","auditLogger: AuditLogger | undefined","options: AuditLoggerOptions","entityType: string","action: string","req: Request","res: Response","next: NextFunction","auditLogContext: AuditLogContext","endpoint: string"],"sources":["../src/types.ts","../src/const.ts","../src/logger.ts","../src/audit-logger.ts","../src/audit-ms.ts","../src/audit-api.ts","../src/index.ts"],"sourcesContent":["/* eslint-disable no-shadow */\nimport type { ModelStatic, Model } from 'sequelize';\nimport type { Sequelize } from 'sequelize-typescript';\nimport type RabbitMq from '@autofleet/rabbit';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport type { IRouter } from 'express';\n\nexport type AuditLoggerOptions = {\n rabbit: RabbitMq;\n sequelize: Sequelize;\n logger: LoggerInstanceManager;\n router?: IRouter;\n entityScopedModelMap?: { [modelName: string]: ModelStatic<Model> };\n};\n\nexport interface AuditLogContext {\n entityType: string;\n entityId?: string;\n action: string;\n performedBy: string;\n endpoint: string;\n method: string;\n actionOrigin?: string;\n}\n\nexport interface AuditLogRow {\n property: string;\n previousValue: any;\n newValue: any;\n}\n\nexport interface AuditLogPayload {\n entityType: string;\n entityId: string;\n rows: AuditLogRow[];\n}\n\nexport enum Action {\n CREATE = 'create',\n BULK_CREATE = 'bulk-create',\n BULK_EDIT = 'bulk-edit',\n DELETE = 'delete',\n UPDATE = 'update',\n CANCEL = 'cancel',\n FAIL = 'fail',\n UNASSIGN = 'unassign',\n BULK_ASSIGN = 'bulk-assign',\n REASSIGN = 'reassign',\n DISPATCH = 'dispatch',\n BULK_DISPATCH = 'bulk-dispatch',\n BULK_UPSERT = 'bulk-upsert',\n UPSERT = 'upsert',\n JOIN = 'join',\n MOVE = 'move',\n REOPTIMIZATION = 'reoptimization',\n}\n\nexport enum EntityType {\n RIDE = 'Ride',\n VEHICLE = 'Vehicle',\n DRIVER = 'Driver',\n PRICE_CALCULATION = 'PriceCalculation',\n}\n\nexport const enum ActionOrigin {\n USER = 'user',\n AUTOMATION = 'automation',\n}\n","export const AUDIT_LOG_CONTEXT_QUEUE = 'audit-log-context';\nexport const AUDIT_LOG_ROWS_QUEUE = 'audit-log-rows';\nexport const AUDIT_LOG_CONTEXT_KEY = 'auditLogContext';\nexport const AUTOMATION_ID_HEADER = 'x-af-automation-id';\n","import Logger, { type LoggerInstanceManager } from '@autofleet/logger';\n\nconst logger: LoggerInstanceManager = Logger(null);\nexport default logger;\n","import type RabbitMq from '@autofleet/rabbit';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport { getCurrentPayload as getCurrentTrace } from '@autofleet/zehut';\nimport type {\n CreateOptions, InstanceUpdateOptions, Model, Sequelize,\n} from 'sequelize';\nimport { isEqual } from 'lodash';\nimport {\n type AuditLogPayload, type AuditLoggerOptions, type AuditLogContext, Action,\n} from './types';\nimport { AUDIT_LOG_CONTEXT_QUEUE, AUDIT_LOG_ROWS_QUEUE, AUDIT_LOG_CONTEXT_KEY } from './const';\nimport logger from './logger';\n\nconst CUSTOM_FIELDS_PROPERTY = 'customFields';\n\nconst getAuditContext = () => {\n const currentTrace = getCurrentTrace();\n const auditContext = currentTrace?.nonHeaderContext?.get(AUDIT_LOG_CONTEXT_KEY);\n return auditContext as AuditLogContext;\n};\n\nconst ACTIONS_TO_OMIT_ENTITY_ID_FROM_CONTEXT = {\n [Action.BULK_CREATE]: Action.BULK_CREATE,\n [Action.BULK_EDIT]: Action.BULK_EDIT,\n [Action.BULK_ASSIGN]: Action.BULK_ASSIGN,\n [Action.BULK_DISPATCH]: Action.BULK_DISPATCH,\n [Action.BULK_UPSERT]: Action.BULK_UPSERT,\n};\n\nconst isEmpty = (field) => {\n if ([null, undefined].includes(field)) {\n return true;\n }\n if (Array.isArray(field)) {\n return field.length === 0;\n }\n if (typeof field === 'object' && !(field instanceof Date)) {\n return Object.keys(field).length === 0;\n }\n return false;\n};\n\nexport const isEntityIdRequired = (action: string): boolean => !ACTIONS_TO_OMIT_ENTITY_ID_FROM_CONTEXT[action];\n\nconst filterOutEmptyFields = (fields: string[], instance) => fields.filter((field) => !isEmpty(instance.get(field)) || !isEmpty(instance.previous(field)));\n\nconst getChangedFieldsRows = (instance: Model, options: CreateOptions<any> | InstanceUpdateOptions<any>) => {\n // When bulk updating in sequelize using the \"returning\" option, the instance.changed() stops working.\n // It's a known issue with sequelize.\n const changedFields: string[] = options.returning ? options.fields as string[] : instance.changed() as string[];\n // Filter customFields - we'll handle them later\n const filteredChangedFields = filterOutEmptyFields(changedFields.filter((field) => field !== CUSTOM_FIELDS_PROPERTY), instance);\n return filteredChangedFields.map((property: string) => ({\n property,\n previousValue: instance.previous(property),\n newValue: instance.get(property),\n }));\n};\n\nconst getChangedCustomFieldsRows = (instance: Model) => {\n const changed = instance.changed();\n const customFieldsChanged = changed && changed.includes(CUSTOM_FIELDS_PROPERTY);\n if (!customFieldsChanged) {\n return [];\n }\n\n const customFields = instance.get(CUSTOM_FIELDS_PROPERTY);\n const previousCustomFields = instance.previous(CUSTOM_FIELDS_PROPERTY);\n const changedCustomFields = Object.keys(customFields).filter((field) => {\n const newValue = customFields?.[field];\n const oldValue = previousCustomFields?.[field];\n return (!isEmpty(newValue) || !isEmpty(oldValue)) && !isEqual(newValue, oldValue);\n });\n return changedCustomFields.map((changedCustomField) => ({\n property: `${CUSTOM_FIELDS_PROPERTY}.${changedCustomField}`,\n previousValue: previousCustomFields?.[changedCustomField],\n newValue: customFields?.[changedCustomField],\n }));\n};\n\nclass AuditLogger {\n private rabbit: RabbitMq;\n\n private sequelize: Sequelize;\n\n private logger: LoggerInstanceManager;\n\n constructor(options: AuditLoggerOptions) {\n this.rabbit = options.rabbit;\n this.sequelize = options.sequelize;\n this.logger = options.logger || logger;\n }\n\n public registerHooks(): void {\n Object.entries(this.sequelize.models).forEach(([modelName, modelType]) => {\n modelType.addHook('afterSave', async (instance, options) => {\n try {\n const auditContext = getAuditContext();\n if (!auditContext) {\n return;\n }\n if (auditContext?.entityType?.toLowerCase() === modelName?.toLowerCase() && isEntityIdRequired(auditContext.action)) {\n auditContext.entityId = (instance as any).id;\n }\n const changedFieldsRows = getChangedFieldsRows(instance, options);\n const changedCustomFieldsRows = getChangedCustomFieldsRows(instance);\n const payload: AuditLogPayload = {\n entityType: modelName,\n entityId: (instance as any).id,\n rows: [...changedFieldsRows, ...changedCustomFieldsRows],\n };\n this.sendAuditLogRows(payload);\n } catch (error) {\n this.logger.error('Failed to send audit log rows', error);\n }\n });\n });\n }\n\n public async sendAuditLogContext(context: AuditLogContext): Promise<void> {\n try {\n await this.rabbit.sendToQueue(AUDIT_LOG_CONTEXT_QUEUE, context);\n } catch (err) {\n this.logger.error('Failed to send audit log context', err);\n }\n }\n\n private async sendAuditLogRows(payload: AuditLogPayload): Promise<void> {\n try {\n await this.rabbit.sendToQueue(AUDIT_LOG_ROWS_QUEUE, payload);\n } catch (err) {\n this.logger.error('Failed to send audit log rows', err);\n }\n }\n}\n\nexport default AuditLogger;\n","import Network from '@autofleet/network';\nimport logger from './logger';\n\nconst auditMs = new Network({ serviceName: 'AUDIT_MS', timeout: 60_000, logger });\n\nconst getByEntityId = async (entityId: string): Promise<any> => {\n const { data } = await auditMs.get(`api/v1/audit-logs/${entityId}`);\n return data;\n};\n\nconst exportObj: { getByEntityId: typeof getByEntityId } = { getByEntityId };\n\nexport default exportObj;\n","import { handleError, ResourceNotFoundError, UnexpectedError } from '@autofleet/errors';\nimport auditMs from './audit-ms';\nimport { AuditLoggerOptions } from './types';\n\nexport default async ({\n router,\n logger,\n entityScopedModelMap,\n}: AuditLoggerOptions): Promise<void> => {\n if (!entityScopedModelMap || !router) {\n return;\n }\n Object.entries(entityScopedModelMap).forEach(([entity, ScopedModel]) => {\n router.get(`${entity}/:id/audit`, async (req, res) => {\n try {\n const { id } = req.params;\n const entityData = await ScopedModel.findByPk(id);\n if (!entityData) {\n return handleError(new ResourceNotFoundError(), res, { logger, message: `Entity ${entity} with id ${id} not found` });\n }\n const auditData = auditMs.getByEntityId(id);\n return res.json(auditData);\n } catch (err) {\n return handleError(new UnexpectedError(err as Error), res, { logger });\n }\n });\n });\n};\n","import { getCurrentPayload as getCurrentTrace, getUser } from '@autofleet/zehut';\nimport type { Request, Response, NextFunction } from 'express';\nimport AuditLogger, { isEntityIdRequired } from './audit-logger';\nimport { ActionOrigin, type AuditLogContext, type AuditLoggerOptions } from './types';\nimport { AUDIT_LOG_CONTEXT_KEY, AUTOMATION_ID_HEADER } from './const';\nimport addAuditApi from './audit-api';\nimport logger from './logger';\n\nlet auditLogger: AuditLogger | undefined;\n\nexport const enableAuditing = (options: AuditLoggerOptions): void => {\n if (process.env.DISABLE_AUDIT_LOGS === 'true') {\n return;\n }\n auditLogger = new AuditLogger(options);\n addAuditApi(options);\n auditLogger.registerHooks();\n};\n\nexport const setAuditContext = (\n entityType: string,\n action: string,\n) => async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n if (process.env.DISABLE_AUDIT_LOGS === 'true' || !auditLogger) {\n return next();\n }\n const user = getUser();\n const automationId = req.headers[AUTOMATION_ID_HEADER] as string | undefined;\n const { performedBy, actionOrigin } = user?.id\n ? { performedBy: user.id, actionOrigin: ActionOrigin.USER }\n : { performedBy: automationId ?? null, actionOrigin: automationId ? ActionOrigin.AUTOMATION : null };\n const auditLogContext: AuditLogContext = {\n entityType,\n action,\n endpoint: req.url,\n method: req.method,\n performedBy,\n actionOrigin,\n };\n\n const currentTrace = getCurrentTrace();\n currentTrace.nonHeaderContext.set(AUDIT_LOG_CONTEXT_KEY, auditLogContext);\n\n const sendAuditLogContextEvent = async () => {\n res.off('finish', sendAuditLogContextEvent);\n res.off('close', sendAuditLogContextEvent);\n res.off('error', sendAuditLogContextEvent);\n await auditLogger.sendAuditLogContext(auditLogContext);\n };\n if (!isEntityIdRequired(action)) { // if it's a bulk action, we don't want to wait for the response to add the entity id\n await auditLogger.sendAuditLogContext(auditLogContext);\n } else {\n res.once('finish', sendAuditLogContextEvent);\n res.once('close', sendAuditLogContextEvent);\n res.once('error', sendAuditLogContextEvent);\n }\n } catch (err) {\n logger.error('coudln\\'t set audit context', err);\n }\n return next();\n};\n\nexport const setRabbitAuditContext = (\n entityType: string,\n action: string,\n) => async (endpoint: string, { userId, automationId }: { userId:string; automationId:string; }): Promise<void> => {\n if (process.env.DISABLE_AUDIT_LOGS === 'true' || !auditLogger) {\n return;\n }\n const currentTrace = getCurrentTrace();\n if (currentTrace?.context?.get) {\n const { performedBy, actionOrigin } = userId\n ? { performedBy: userId, actionOrigin: ActionOrigin.USER }\n : { performedBy: automationId ?? null, actionOrigin: automationId ? ActionOrigin.AUTOMATION : null };\n const auditLogContext: AuditLogContext = {\n entityType,\n action,\n endpoint,\n method: 'rabbit',\n performedBy,\n actionOrigin,\n };\n currentTrace.nonHeaderContext.set(AUDIT_LOG_CONTEXT_KEY, auditLogContext);\n await auditLogger.sendAuditLogContext(auditLogContext);\n }\n};\n\nexport * from './types';\nexport * from './const';\n\nexport default AuditLogger;\n"],"mappings":"usBAgEA,IA3BY,kBAAA,SAAA,EAAL,QACL,EAAA,OAAA,SACA,EAAA,YAAA,cACA,EAAA,UAAA,YACA,EAAA,OAAA,SACA,EAAA,OAAA,SACA,EAAA,OAAA,SACA,EAAA,KAAA,OACA,EAAA,SAAA,WACA,EAAA,YAAA,cACA,EAAA,SAAA,WACA,EAAA,SAAA,WACA,EAAA,cAAA,gBACA,EAAA,YAAA,cACA,EAAA,OAAA,SACA,EAAA,KAAA,OACA,EAAA,KAAA,OACA,EAAA,eAAA,kBACD,EAAA,CAAA,EAAA,CAEW,kBAAA,SAAA,EAAL,QACL,EAAA,KAAA,OACA,EAAA,QAAA,UACA,EAAA,OAAA,SACA,EAAA,kBAAA,oBACD,EAAA,CAAA,EAAA,CAEiB,kBAAA,SAAA,EAAX,QACL,EAAA,KAAA,OACA,EAAA,WAAA,cACD,EAAA,CAAA,EAAA,CEjED,MDFa,EAA0B,oBAC1B,EAAuB,iBACvB,EAAwB,kBACxB,EAAuB,qBCD9BA,EAAgC,CAAA,EAAA,EAAA,SAAO,KAAK,CAClD,IAAA,EAAe,ECwDf,MA9CM,EAAyB,eAEzB,EAAkB,IAAM,CAE5B,IADM,EAAe,CAAA,EAAA,EAAA,oBAAiB,CAChC,EAAe,GAAc,kBAAkB,IAAI,EAAsB,CAC/E,OAAO,CACR,EAEK,EAAyC,EAC5C,EAAO,aAAc,EAAO,aAC5B,EAAO,WAAY,EAAO,WAC1B,EAAO,aAAc,EAAO,aAC5B,EAAO,eAAgB,EAAO,eAC9B,EAAO,aAAc,EAAO,WAC9B,EAEK,EAAU,AAAC,GACX,CAAC,SAAA,EAAgB,EAAC,SAAS,EAAM,EAC5B,EAEL,MAAM,QAAQ,EAAM,CACf,EAAM,SAAW,SAEf,GAAU,YAAc,aAAiB,MAC3C,OAAO,KAAK,EAAM,CAAC,SAAW,GAEhC,EAGI,EAAqB,AAACe,IAA6B,EAAuC,GAEjG,EAAuB,CAACb,EAAkB,IAAa,EAAO,OAAO,AAAC,IAAW,EAAQ,EAAS,IAAI,EAAM,CAAC,GAAK,EAAQ,EAAS,SAAS,EAAM,CAAC,CAAC,CAEpJ,EAAuB,CAACC,EAAiBC,IAA6D,CAK1G,IAFMC,EAA0B,EAAQ,UAAY,EAAQ,OAAqB,EAAS,SAAS,CAE7F,EAAwB,EAAqB,EAAc,OAAO,AAAC,GAAU,IAAU,EAAuB,CAAE,EAAS,CAC/H,MAAO,GAAsB,IAAI,AAACC,IAAsB,CACtD,WACA,cAAe,EAAS,SAAS,EAAS,CAC1C,SAAU,EAAS,IAAI,EAAS,AACjC,GAAE,AACJ,EAEK,EAA6B,AAACH,GAAoB,CAEtD,IADM,EAAU,EAAS,SAAS,CAC5B,EAAsB,GAAW,EAAQ,SAAS,EAAuB,CAC/E,IAAK,EACH,MAAO,CAAE,EAKX,IAFM,EAAe,EAAS,IAAI,EAAuB,CACnD,EAAuB,EAAS,SAAS,EAAuB,CAChE,EAAsB,OAAO,KAAK,EAAa,CAAC,OAAO,AAAC,GAAU,CAEtE,IADM,EAAW,IAAe,GAC1B,EAAW,IAAuB,GACxC,QAAS,EAAQ,EAAS,GAAK,EAAQ,EAAS,IAAM,CAAA,EAAA,EAAA,SAAQ,EAAU,EAAS,AAClF,EAAC,CACF,MAAO,GAAoB,IAAI,AAAC,IAAwB,CACtD,UAAW,EAAE,EAAuB,GAAG,EAAmB,EAC1D,cAAe,IAAuB,GACtC,SAAU,IAAe,EAC1B,GAAE,AACJ,EA0DD,IAxDM,EAAN,KAAkB,CAOhB,YAAYU,EAA6B,CAGvC,AAFA,KAAK,OAAS,EAAQ,OACtB,KAAK,UAAY,EAAQ,UACzB,KAAK,OAAS,EAAQ,QAAU,CACjC,CAED,eAA6B,CAC3B,OAAO,QAAQ,KAAK,UAAU,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAW,EAAU,GAAK,CACxE,EAAU,QAAQ,YAAa,MAAO,EAAU,IAAY,CAC1D,GAAI,CACF,IAAM,EAAe,GAAiB,CACtC,IAAK,EACH,OAEF,AAAI,GAAc,YAAY,aAAa,GAAK,GAAW,aAAa,EAAI,EAAmB,EAAa,OAAO,GACjH,EAAa,SAAY,EAAiB,IAI5C,IAFM,EAAoB,EAAqB,EAAU,EAAQ,CAC3D,EAA0B,EAA2B,EAAS,CAC9DL,EAA2B,CAC/B,WAAY,EACZ,SAAW,EAAiB,GAC5B,KAAM,CAAC,GAAG,EAAmB,GAAG,CAAwB,CACzD,EACD,KAAK,iBAAiB,EAAQ,AAC/B,OAAQ,EAAO,CACd,KAAK,OAAO,MAAM,gCAAiC,EAAM,AAC1D,CACF,EAAC,AACH,EAAC,AACH,CAED,MAAa,oBAAoBC,EAAyC,CACxE,GAAI,CACF,KAAM,MAAK,OAAO,YAAY,EAAyB,EAAQ,AAChE,OAAQ,EAAK,CACZ,KAAK,OAAO,MAAM,mCAAoC,EAAI,AAC3D,CACF,CAED,MAAc,iBAAiBD,EAAyC,CACtE,GAAI,CACF,KAAM,MAAK,OAAO,YAAY,EAAsB,EAAQ,AAC7D,OAAQ,EAAK,CACZ,KAAK,OAAO,MAAM,gCAAiC,EAAI,AACxD,CACF,CACF,EAED,EAAe,EC9Hf,MAPM,EAAU,IAAI,EAAA,QAAQ,CAAE,YAAa,WAAY,QAAS,IAAQ,OAAA,CAAQ,GAE1E,EAAgB,MAAOE,GAAmC,CAC9D,GAAM,CAAE,OAAM,CAAG,KAAM,GAAQ,KAAK,oBAAoB,EAAS,EAAE,CACnE,OAAO,CACR,EAEKC,EAAqD,CAAE,eAAe,ECN5E,IDQA,EAAe,ECRf,EAAe,MAAO,CACpB,SACA,OAAA,EACA,uBACmB,GAAoB,CACvC,CAAK,IAAyB,GAG9B,OAAO,QAAQ,EAAqB,CAAC,QAAQ,CAAC,CAAC,EAAQ,EAAY,GAAK,CACtE,EAAO,KAAK,EAAE,EAAO,YAAa,MAAO,EAAK,IAAQ,CACpD,GAAI,CAEF,GADM,CAAE,KAAI,CAAG,EAAI,OACb,EAAa,KAAM,GAAY,SAAS,EAAG,CACjD,IAAK,EACH,MAAO,CAAA,EAAA,EAAA,aAAY,IAAI,EAAA,sBAAyB,EAAK,CAAE,OAAA,EAAQ,SAAU,SAAS,EAAO,WAAW,EAAG,WAAa,EAAC,CAEvH,IAAM,EAAY,EAAQ,cAAc,EAAG,CAC3C,MAAO,GAAI,KAAK,EAAU,AAC3B,OAAQ,EAAK,CACZ,MAAO,CAAA,EAAA,EAAA,aAAY,IAAI,EAAA,gBAAgB,GAAe,EAAK,CAAE,OAAA,CAAQ,EAAC,AACvE,CACF,EAAC,AACH,EAAC,AACH,ECnBD,IAAIC,EAuDJ,MArDa,EAAiB,AAACC,GAAsC,CAC/D,QAAQ,IAAI,qBAAuB,SAGvC,EAAc,IAAI,EAAY,GAC9B,EAAY,EAAQ,CACpB,EAAY,eAAe,CAC5B,EAEY,EAAkB,CAC7BC,EACAC,IACG,MAAOC,EAAcC,EAAeC,IAAsC,CAC7E,GAAI,CACF,GAAI,QAAQ,IAAI,qBAAuB,SAAW,EAChD,MAAO,IAAM,CAgBf,IAdM,EAAO,CAAA,EAAA,EAAA,UAAS,CAChB,EAAe,EAAI,QAAQ,GAC3B,CAAE,cAAa,eAAc,CAAG,GAAM,GACxC,CAAE,YAAa,EAAK,GAAI,aAAc,EAAa,IAAM,EACzD,CAAE,YAAa,GAAgB,KAAM,aAAc,EAAe,EAAa,WAAa,IAAM,EAChGC,EAAmC,CACvC,aACA,SACA,SAAU,EAAI,IACd,OAAQ,EAAI,OACZ,cACA,cACD,EAEK,EAAe,CAAA,EAAA,EAAA,oBAAiB,CACtC,EAAa,iBAAiB,IAAI,EAAuB,EAAgB,CAEzE,IAAM,EAA2B,SAAY,CAI3C,AAHA,EAAI,IAAI,SAAU,EAAyB,CAC3C,EAAI,IAAI,QAAS,EAAyB,CAC1C,EAAI,IAAI,QAAS,EAAyB,CAC1C,KAAM,GAAY,oBAAoB,EAAgB,AACvD,EACD,AAAK,EAAmB,EAAO,EAG7B,EAAI,KAAK,SAAU,EAAyB,CAC5C,EAAI,KAAK,QAAS,EAAyB,CAC3C,EAAI,KAAK,QAAS,EAAyB,EAJ3C,KAAM,GAAY,oBAAoB,EAAgB,AAMzD,OAAQ,EAAK,CACZ,EAAO,MAAM,6BAA+B,EAAI,AACjD,CACD,MAAO,IAAM,AACd,EAEY,EAAwB,CACnCL,EACAC,IACG,MAAOK,EAAkB,CAAE,SAAQ,eAAuD,GAAoB,CACjH,GAAI,QAAQ,IAAI,qBAAuB,SAAW,EAChD,OAEF,IAAM,EAAe,CAAA,EAAA,EAAA,oBAAiB,CACtC,GAAI,GAAc,SAAS,IAAK,CAI9B,GAHM,CAAE,cAAa,eAAc,CAAG,EAClC,CAAE,YAAa,EAAQ,aAAc,EAAa,IAAM,EACxD,CAAE,YAAa,GAAgB,KAAM,aAAc,EAAe,EAAa,WAAa,IAAM,EAChGD,EAAmC,CACvC,aACA,SACA,WACA,OAAQ,SACR,cACA,cACD,EAED,AADA,EAAa,iBAAiB,IAAI,EAAuB,EAAgB,CACzE,KAAM,GAAY,oBAAoB,EAAgB,AACvD,CACF,EAKD,IAAA,EAAe"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["logger: LoggerInstanceManager","action: string","fields: string[]","instance: Model","options: CreateOptions<any> | InstanceUpdateOptions<any>","changedFields: string[]","property: string","options: AuditLoggerOptions","payload: AuditLogPayload","context: AuditLogContext","entityId: string","exportObj: { getByEntityId: typeof getByEntityId }","auditLogger: AuditLogger | undefined","options: AuditLoggerOptions","entityType: string","action: string","req: Request","res: Response","next: NextFunction","auditLogContext: AuditLogContext","endpoint: string"],"sources":["../src/types.ts","../src/const.ts","../src/logger.ts","../src/audit-logger.ts","../src/audit-ms.ts","../src/audit-api.ts","../src/index.ts"],"sourcesContent":["/* eslint-disable no-shadow */\nimport type { ModelStatic, Model } from 'sequelize';\nimport type { Sequelize } from 'sequelize-typescript';\nimport type RabbitMq from '@autofleet/rabbit';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport type { IRouter } from 'express';\n\nexport type AuditLoggerOptions = {\n rabbit: RabbitMq;\n sequelize: Sequelize;\n logger: LoggerInstanceManager;\n router?: IRouter;\n excludeModels?: string[];\n entityScopedModelMap?: { [modelName: string]: ModelStatic<Model> };\n};\n\nexport interface AuditLogContext {\n entityType: string;\n entityId?: string;\n action: string;\n performedBy: string;\n endpoint: string;\n method: string;\n actionOrigin?: string;\n}\n\nexport interface AuditLogRow {\n property: string;\n previousValue: any;\n newValue: any;\n}\n\nexport interface AuditLogPayload {\n entityType: string;\n entityId: string;\n rows: AuditLogRow[];\n}\n\nexport enum Action {\n CREATE = 'create',\n BULK_CREATE = 'bulk-create',\n BULK_EDIT = 'bulk-edit',\n DELETE = 'delete',\n UPDATE = 'update',\n CANCEL = 'cancel',\n FAIL = 'fail',\n UNASSIGN = 'unassign',\n BULK_ASSIGN = 'bulk-assign',\n REASSIGN = 'reassign',\n DISPATCH = 'dispatch',\n BULK_DISPATCH = 'bulk-dispatch',\n BULK_UPSERT = 'bulk-upsert',\n UPSERT = 'upsert',\n JOIN = 'join',\n MOVE = 'move',\n REOPTIMIZATION = 'reoptimization',\n}\n\nexport enum EntityType {\n RIDE = 'Ride',\n VEHICLE = 'Vehicle',\n DRIVER = 'Driver',\n PRICE_CALCULATION = 'PriceCalculation',\n}\n\nexport const enum ActionOrigin {\n USER = 'user',\n AUTOMATION = 'automation',\n}\n","export const AUDIT_LOG_CONTEXT_QUEUE = 'audit-log-context';\nexport const AUDIT_LOG_ROWS_QUEUE = 'audit-log-rows';\nexport const AUDIT_LOG_CONTEXT_KEY = 'auditLogContext';\nexport const AUTOMATION_ID_HEADER = 'x-af-automation-id';\n","import Logger, { type LoggerInstanceManager } from '@autofleet/logger';\n\nconst logger: LoggerInstanceManager = Logger(null);\nexport default logger;\n","import type RabbitMq from '@autofleet/rabbit';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport { getCurrentPayload as getCurrentTrace } from '@autofleet/zehut';\nimport type {\n CreateOptions, InstanceUpdateOptions, Model, Sequelize,\n} from 'sequelize';\nimport { isEqual } from 'lodash';\nimport {\n type AuditLogPayload, type AuditLoggerOptions, type AuditLogContext, Action,\n} from './types';\nimport { AUDIT_LOG_CONTEXT_QUEUE, AUDIT_LOG_ROWS_QUEUE, AUDIT_LOG_CONTEXT_KEY } from './const';\nimport logger from './logger';\n\nconst CUSTOM_FIELDS_PROPERTY = 'customFields';\n\nconst getAuditContext = () => {\n const currentTrace = getCurrentTrace();\n const auditContext = currentTrace?.nonHeaderContext?.get(AUDIT_LOG_CONTEXT_KEY);\n return auditContext as AuditLogContext;\n};\n\nconst ACTIONS_TO_OMIT_ENTITY_ID_FROM_CONTEXT = {\n [Action.BULK_CREATE]: Action.BULK_CREATE,\n [Action.BULK_EDIT]: Action.BULK_EDIT,\n [Action.BULK_ASSIGN]: Action.BULK_ASSIGN,\n [Action.BULK_DISPATCH]: Action.BULK_DISPATCH,\n [Action.BULK_UPSERT]: Action.BULK_UPSERT,\n};\n\nconst isEmpty = (field) => {\n if ([null, undefined].includes(field)) {\n return true;\n }\n if (Array.isArray(field)) {\n return field.length === 0;\n }\n if (typeof field === 'object' && !(field instanceof Date)) {\n return Object.keys(field).length === 0;\n }\n return false;\n};\n\nexport const isEntityIdRequired = (action: string): boolean => !ACTIONS_TO_OMIT_ENTITY_ID_FROM_CONTEXT[action];\n\nconst filterOutEmptyFields = (fields: string[], instance) => fields.filter((field) => !isEmpty(instance.get(field)) || !isEmpty(instance.previous(field)));\n\nconst getChangedFieldsRows = (instance: Model, options: CreateOptions<any> | InstanceUpdateOptions<any>) => {\n // When bulk updating in sequelize using the \"returning\" option, the instance.changed() stops working.\n // It's a known issue with sequelize.\n const changedFields: string[] = options.returning ? options.fields as string[] : instance.changed() as string[];\n // Filter customFields - we'll handle them later\n const filteredChangedFields = filterOutEmptyFields(changedFields.filter((field) => field !== CUSTOM_FIELDS_PROPERTY), instance);\n return filteredChangedFields.map((property: string) => ({\n property,\n previousValue: instance.previous(property),\n newValue: instance.get(property),\n }));\n};\n\nconst getChangedCustomFieldsRows = (instance: Model) => {\n const changed = instance.changed();\n const customFieldsChanged = changed && changed.includes(CUSTOM_FIELDS_PROPERTY);\n if (!customFieldsChanged) {\n return [];\n }\n\n const customFields = instance.get(CUSTOM_FIELDS_PROPERTY);\n const previousCustomFields = instance.previous(CUSTOM_FIELDS_PROPERTY);\n const changedCustomFields = Object.keys(customFields).filter((field) => {\n const newValue = customFields?.[field];\n const oldValue = previousCustomFields?.[field];\n return (!isEmpty(newValue) || !isEmpty(oldValue)) && !isEqual(newValue, oldValue);\n });\n return changedCustomFields.map((changedCustomField) => ({\n property: `${CUSTOM_FIELDS_PROPERTY}.${changedCustomField}`,\n previousValue: previousCustomFields?.[changedCustomField],\n newValue: customFields?.[changedCustomField],\n }));\n};\n\nclass AuditLogger {\n private rabbit: RabbitMq;\n\n private sequelize: Sequelize;\n\n private logger: LoggerInstanceManager;\n\n private excludeModels: string[];\n\n constructor(options: AuditLoggerOptions) {\n this.rabbit = options.rabbit;\n this.sequelize = options.sequelize;\n this.logger = options.logger || logger;\n this.excludeModels = options.excludeModels ?? [];\n }\n\n public registerHooks(): void {\n Object.entries(this.sequelize.models).forEach(([modelName, modelType]) => {\n if (this.excludeModels.includes(modelName)) {\n return;\n }\n modelType.addHook('afterSave', async (instance, options) => {\n try {\n const auditContext = getAuditContext();\n if (!auditContext) {\n return;\n }\n if (auditContext?.entityType?.toLowerCase() === modelName?.toLowerCase() && isEntityIdRequired(auditContext.action)) {\n auditContext.entityId = (instance as any).id;\n }\n const changedFieldsRows = getChangedFieldsRows(instance, options);\n const changedCustomFieldsRows = getChangedCustomFieldsRows(instance);\n const payload: AuditLogPayload = {\n entityType: modelName,\n entityId: (instance as any).id,\n rows: [...changedFieldsRows, ...changedCustomFieldsRows],\n };\n this.sendAuditLogRows(payload);\n } catch (error) {\n this.logger.error('Failed to send audit log rows', error);\n }\n });\n });\n }\n\n public async sendAuditLogContext(context: AuditLogContext): Promise<void> {\n try {\n await this.rabbit.sendToQueue(AUDIT_LOG_CONTEXT_QUEUE, context);\n } catch (err) {\n this.logger.error('Failed to send audit log context', err);\n }\n }\n\n private async sendAuditLogRows(payload: AuditLogPayload): Promise<void> {\n try {\n await this.rabbit.sendToQueue(AUDIT_LOG_ROWS_QUEUE, payload);\n } catch (err) {\n this.logger.error('Failed to send audit log rows', err);\n }\n }\n}\n\nexport default AuditLogger;\n","import Network from '@autofleet/network';\nimport logger from './logger';\n\nconst auditMs = new Network({ serviceName: 'AUDIT_MS', timeout: 60_000, logger });\n\nconst getByEntityId = async (entityId: string): Promise<any> => {\n const { data } = await auditMs.get(`api/v1/audit-logs/${entityId}`);\n return data;\n};\n\nconst exportObj: { getByEntityId: typeof getByEntityId } = { getByEntityId };\n\nexport default exportObj;\n","import { handleError, ResourceNotFoundError, UnexpectedError } from '@autofleet/errors';\nimport auditMs from './audit-ms';\nimport { AuditLoggerOptions } from './types';\n\nexport default async ({\n router,\n logger,\n entityScopedModelMap,\n}: AuditLoggerOptions): Promise<void> => {\n if (!entityScopedModelMap || !router) {\n return;\n }\n Object.entries(entityScopedModelMap).forEach(([entity, ScopedModel]) => {\n router.get(`${entity}/:id/audit`, async (req, res) => {\n try {\n const { id } = req.params;\n const entityData = await ScopedModel.findByPk(id);\n if (!entityData) {\n return handleError(new ResourceNotFoundError(), res, { logger, message: `Entity ${entity} with id ${id} not found` });\n }\n const auditData = auditMs.getByEntityId(id);\n return res.json(auditData);\n } catch (err) {\n return handleError(new UnexpectedError(err as Error), res, { logger });\n }\n });\n });\n};\n","import { getCurrentPayload as getCurrentTrace, getUser } from '@autofleet/zehut';\nimport type { Request, Response, NextFunction } from 'express';\nimport AuditLogger, { isEntityIdRequired } from './audit-logger';\nimport { ActionOrigin, type AuditLogContext, type AuditLoggerOptions } from './types';\nimport { AUDIT_LOG_CONTEXT_KEY, AUTOMATION_ID_HEADER } from './const';\nimport addAuditApi from './audit-api';\nimport logger from './logger';\n\nlet auditLogger: AuditLogger | undefined;\n\nexport const enableAuditing = (options: AuditLoggerOptions): void => {\n if (process.env.DISABLE_AUDIT_LOGS === 'true') {\n return;\n }\n auditLogger = new AuditLogger(options);\n addAuditApi(options);\n auditLogger.registerHooks();\n};\n\nexport const setAuditContext = (\n entityType: string,\n action: string,\n) => async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n if (process.env.DISABLE_AUDIT_LOGS === 'true' || !auditLogger) {\n return next();\n }\n const user = getUser();\n const automationId = req.headers[AUTOMATION_ID_HEADER] as string | undefined;\n const { performedBy, actionOrigin } = user?.id\n ? { performedBy: user.id, actionOrigin: ActionOrigin.USER }\n : { performedBy: automationId ?? null, actionOrigin: automationId ? ActionOrigin.AUTOMATION : null };\n const auditLogContext: AuditLogContext = {\n entityType,\n action,\n endpoint: req.url,\n method: req.method,\n performedBy,\n actionOrigin,\n };\n\n const currentTrace = getCurrentTrace();\n currentTrace.nonHeaderContext.set(AUDIT_LOG_CONTEXT_KEY, auditLogContext);\n\n const sendAuditLogContextEvent = async () => {\n res.off('finish', sendAuditLogContextEvent);\n res.off('close', sendAuditLogContextEvent);\n res.off('error', sendAuditLogContextEvent);\n await auditLogger.sendAuditLogContext(auditLogContext);\n };\n if (!isEntityIdRequired(action)) { // if it's a bulk action, we don't want to wait for the response to add the entity id\n await auditLogger.sendAuditLogContext(auditLogContext);\n } else {\n res.once('finish', sendAuditLogContextEvent);\n res.once('close', sendAuditLogContextEvent);\n res.once('error', sendAuditLogContextEvent);\n }\n } catch (err) {\n logger.error('coudln\\'t set audit context', err);\n }\n return next();\n};\n\nexport const setRabbitAuditContext = (\n entityType: string,\n action: string,\n) => async (endpoint: string, { userId, automationId }: { userId:string; automationId:string; }): Promise<void> => {\n if (process.env.DISABLE_AUDIT_LOGS === 'true' || !auditLogger) {\n return;\n }\n const currentTrace = getCurrentTrace();\n if (currentTrace?.context?.get) {\n const { performedBy, actionOrigin } = userId\n ? { performedBy: userId, actionOrigin: ActionOrigin.USER }\n : { performedBy: automationId ?? null, actionOrigin: automationId ? ActionOrigin.AUTOMATION : null };\n const auditLogContext: AuditLogContext = {\n entityType,\n action,\n endpoint,\n method: 'rabbit',\n performedBy,\n actionOrigin,\n };\n currentTrace.nonHeaderContext.set(AUDIT_LOG_CONTEXT_KEY, auditLogContext);\n await auditLogger.sendAuditLogContext(auditLogContext);\n }\n};\n\nexport * from './types';\nexport * from './const';\n\nexport default AuditLogger;\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -10,6 +10,7 @@ type AuditLoggerOptions = {
|
|
|
10
10
|
sequelize: Sequelize;
|
|
11
11
|
logger: LoggerInstanceManager;
|
|
12
12
|
router?: IRouter;
|
|
13
|
+
excludeModels?: string[];
|
|
13
14
|
entityScopedModelMap?: {
|
|
14
15
|
[modelName: string]: ModelStatic<Model>;
|
|
15
16
|
};
|
|
@@ -67,6 +68,7 @@ declare class AuditLogger {
|
|
|
67
68
|
private rabbit;
|
|
68
69
|
private sequelize;
|
|
69
70
|
private logger;
|
|
71
|
+
private excludeModels;
|
|
70
72
|
constructor(options: AuditLoggerOptions);
|
|
71
73
|
registerHooks(): void;
|
|
72
74
|
sendAuditLogContext(context: AuditLogContext): Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ type AuditLoggerOptions = {
|
|
|
10
10
|
sequelize: Sequelize;
|
|
11
11
|
logger: LoggerInstanceManager;
|
|
12
12
|
router?: IRouter;
|
|
13
|
+
excludeModels?: string[];
|
|
13
14
|
entityScopedModelMap?: {
|
|
14
15
|
[modelName: string]: ModelStatic<Model>;
|
|
15
16
|
};
|
|
@@ -67,6 +68,7 @@ declare class AuditLogger {
|
|
|
67
68
|
private rabbit;
|
|
68
69
|
private sequelize;
|
|
69
70
|
private logger;
|
|
71
|
+
private excludeModels;
|
|
70
72
|
constructor(options: AuditLoggerOptions);
|
|
71
73
|
registerHooks(): void;
|
|
72
74
|
sendAuditLogContext(context: AuditLogContext): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getCurrentPayload as e,getUser as t}from"@autofleet/zehut";import{isEqual as n}from"lodash";import r from"@autofleet/logger";import{ResourceNotFoundError as i,UnexpectedError as a,handleError as o}from"@autofleet/errors";import s from"@autofleet/network";let c=/* @__PURE__ */ function(e){return e.CREATE=`create`,e.BULK_CREATE=`bulk-create`,e.BULK_EDIT=`bulk-edit`,e.DELETE=`delete`,e.UPDATE=`update`,e.CANCEL=`cancel`,e.FAIL=`fail`,e.UNASSIGN=`unassign`,e.BULK_ASSIGN=`bulk-assign`,e.REASSIGN=`reassign`,e.DISPATCH=`dispatch`,e.BULK_DISPATCH=`bulk-dispatch`,e.BULK_UPSERT=`bulk-upsert`,e.UPSERT=`upsert`,e.JOIN=`join`,e.MOVE=`move`,e.REOPTIMIZATION=`reoptimization`,e}({}),l=/* @__PURE__ */ function(e){return e.RIDE=`Ride`,e.VEHICLE=`Vehicle`,e.DRIVER=`Driver`,e.PRICE_CALCULATION=`PriceCalculation`,e}({}),u=/* @__PURE__ */ function(e){return e.USER=`user`,e.AUTOMATION=`automation`,e}({});const d=`audit-log-context`,f=`audit-log-rows`,p=`auditLogContext`,m=`x-af-automation-id`,h=r(null);var g=h;const _=`customFields`,v=()=>{let t=e(),n=t?.nonHeaderContext?.get(p);return n},y={[c.BULK_CREATE]:c.BULK_CREATE,[c.BULK_EDIT]:c.BULK_EDIT,[c.BULK_ASSIGN]:c.BULK_ASSIGN,[c.BULK_DISPATCH]:c.BULK_DISPATCH,[c.BULK_UPSERT]:c.BULK_UPSERT},b=e=>[null,void 0].includes(e)?!0:Array.isArray(e)?e.length===0:typeof e==`object`&&!(e instanceof Date)?Object.keys(e).length===0:!1,x=e=>!y[e],S=(e,t)=>e.filter(e=>!b(t.get(e))||!b(t.previous(e))),C=(e,t)=>{let n=t.returning?t.fields:e.changed(),r=S(n.filter(e=>e!==_),e);return r.map(t=>({property:t,previousValue:e.previous(t),newValue:e.get(t)}))},w=e=>{let t=e.changed(),r=t&&t.includes(_);if(!r)return[];let i=e.get(_),a=e.previous(_),o=Object.keys(i).filter(e=>{let t=i?.[e],r=a?.[e];return(!b(t)||!b(r))&&!n(t,r)});return o.map(e=>({property:`${_}.${e}`,previousValue:a?.[e],newValue:i?.[e]}))};var T=class{constructor(e){this.rabbit=e.rabbit,this.sequelize=e.sequelize,this.logger=e.logger||g}registerHooks(){Object.entries(this.sequelize.models).forEach(([e,t])=>{t.addHook(`afterSave`,async(t,n)=>{try{let r=v();if(!r)return;r?.entityType?.toLowerCase()===e?.toLowerCase()&&x(r.action)&&(r.entityId=t.id);let i=C(t,n),a=w(t),o={entityType:e,entityId:t.id,rows:[...i,...a]};this.sendAuditLogRows(o)}catch(e){this.logger.error(`Failed to send audit log rows`,e)}})})}async sendAuditLogContext(e){try{await this.rabbit.sendToQueue(d,e)}catch(e){this.logger.error(`Failed to send audit log context`,e)}}async sendAuditLogRows(e){try{await this.rabbit.sendToQueue(f,e)}catch(e){this.logger.error(`Failed to send audit log rows`,e)}}},E=T;const D=new s({serviceName:`AUDIT_MS`,timeout:6e4,logger:g}),O=async e=>{let{data:t}=await D.get(`api/v1/audit-logs/${e}`);return t},k={getByEntityId:O};var A=k,j=async({router:e,logger:t,entityScopedModelMap:n})=>{!n||!e||Object.entries(n).forEach(([n,r])=>{e.get(`${n}/:id/audit`,async(e,s)=>{try{let{id:a}=e.params,c=await r.findByPk(a);if(!c)return o(new i,s,{logger:t,message:`Entity ${n} with id ${a} not found`});let l=A.getByEntityId(a);return s.json(l)}catch(e){return o(new a(e),s,{logger:t})}})})};let M;const N=e=>{process.env.DISABLE_AUDIT_LOGS!==`true`&&(M=new E(e),j(e),M.registerHooks())},P=(n,r)=>async(i,a,o)=>{try{if(process.env.DISABLE_AUDIT_LOGS===`true`||!M)return o();let s=t(),c=i.headers[m],{performedBy:l,actionOrigin:d}=s?.id?{performedBy:s.id,actionOrigin:u.USER}:{performedBy:c??null,actionOrigin:c?u.AUTOMATION:null},f={entityType:n,action:r,endpoint:i.url,method:i.method,performedBy:l,actionOrigin:d},h=e();h.nonHeaderContext.set(p,f);let g=async()=>{a.off(`finish`,g),a.off(`close`,g),a.off(`error`,g),await M.sendAuditLogContext(f)};x(r)?(a.once(`finish`,g),a.once(`close`,g),a.once(`error`,g)):await M.sendAuditLogContext(f)}catch(e){g.error(`coudln't set audit context`,e)}return o()},F=(t,n)=>async(r,{userId:i,automationId:a})=>{if(process.env.DISABLE_AUDIT_LOGS===`true`||!M)return;let o=e();if(o?.context?.get){let{performedBy:e,actionOrigin:s}=i?{performedBy:i,actionOrigin:u.USER}:{performedBy:a??null,actionOrigin:a?u.AUTOMATION:null},c={entityType:t,action:n,endpoint:r,method:`rabbit`,performedBy:e,actionOrigin:s};o.nonHeaderContext.set(p,c),await M.sendAuditLogContext(c)}};var I=E;export{p as AUDIT_LOG_CONTEXT_KEY,d as AUDIT_LOG_CONTEXT_QUEUE,f as AUDIT_LOG_ROWS_QUEUE,m as AUTOMATION_ID_HEADER,c as Action,u as ActionOrigin,l as EntityType,I as default,N as enableAuditing,P as setAuditContext,F as setRabbitAuditContext};
|
|
1
|
+
import{getCurrentPayload as e,getUser as t}from"@autofleet/zehut";import{isEqual as n}from"lodash";import r from"@autofleet/logger";import{ResourceNotFoundError as i,UnexpectedError as a,handleError as o}from"@autofleet/errors";import s from"@autofleet/network";let c=/* @__PURE__ */ function(e){return e.CREATE=`create`,e.BULK_CREATE=`bulk-create`,e.BULK_EDIT=`bulk-edit`,e.DELETE=`delete`,e.UPDATE=`update`,e.CANCEL=`cancel`,e.FAIL=`fail`,e.UNASSIGN=`unassign`,e.BULK_ASSIGN=`bulk-assign`,e.REASSIGN=`reassign`,e.DISPATCH=`dispatch`,e.BULK_DISPATCH=`bulk-dispatch`,e.BULK_UPSERT=`bulk-upsert`,e.UPSERT=`upsert`,e.JOIN=`join`,e.MOVE=`move`,e.REOPTIMIZATION=`reoptimization`,e}({}),l=/* @__PURE__ */ function(e){return e.RIDE=`Ride`,e.VEHICLE=`Vehicle`,e.DRIVER=`Driver`,e.PRICE_CALCULATION=`PriceCalculation`,e}({}),u=/* @__PURE__ */ function(e){return e.USER=`user`,e.AUTOMATION=`automation`,e}({});const d=`audit-log-context`,f=`audit-log-rows`,p=`auditLogContext`,m=`x-af-automation-id`,h=r(null);var g=h;const _=`customFields`,v=()=>{let t=e(),n=t?.nonHeaderContext?.get(p);return n},y={[c.BULK_CREATE]:c.BULK_CREATE,[c.BULK_EDIT]:c.BULK_EDIT,[c.BULK_ASSIGN]:c.BULK_ASSIGN,[c.BULK_DISPATCH]:c.BULK_DISPATCH,[c.BULK_UPSERT]:c.BULK_UPSERT},b=e=>[null,void 0].includes(e)?!0:Array.isArray(e)?e.length===0:typeof e==`object`&&!(e instanceof Date)?Object.keys(e).length===0:!1,x=e=>!y[e],S=(e,t)=>e.filter(e=>!b(t.get(e))||!b(t.previous(e))),C=(e,t)=>{let n=t.returning?t.fields:e.changed(),r=S(n.filter(e=>e!==_),e);return r.map(t=>({property:t,previousValue:e.previous(t),newValue:e.get(t)}))},w=e=>{let t=e.changed(),r=t&&t.includes(_);if(!r)return[];let i=e.get(_),a=e.previous(_),o=Object.keys(i).filter(e=>{let t=i?.[e],r=a?.[e];return(!b(t)||!b(r))&&!n(t,r)});return o.map(e=>({property:`${_}.${e}`,previousValue:a?.[e],newValue:i?.[e]}))};var T=class{constructor(e){this.rabbit=e.rabbit,this.sequelize=e.sequelize,this.logger=e.logger||g,this.excludeModels=e.excludeModels??[]}registerHooks(){Object.entries(this.sequelize.models).forEach(([e,t])=>{this.excludeModels.includes(e)||t.addHook(`afterSave`,async(t,n)=>{try{let r=v();if(!r)return;r?.entityType?.toLowerCase()===e?.toLowerCase()&&x(r.action)&&(r.entityId=t.id);let i=C(t,n),a=w(t),o={entityType:e,entityId:t.id,rows:[...i,...a]};this.sendAuditLogRows(o)}catch(e){this.logger.error(`Failed to send audit log rows`,e)}})})}async sendAuditLogContext(e){try{await this.rabbit.sendToQueue(d,e)}catch(e){this.logger.error(`Failed to send audit log context`,e)}}async sendAuditLogRows(e){try{await this.rabbit.sendToQueue(f,e)}catch(e){this.logger.error(`Failed to send audit log rows`,e)}}},E=T;const D=new s({serviceName:`AUDIT_MS`,timeout:6e4,logger:g}),O=async e=>{let{data:t}=await D.get(`api/v1/audit-logs/${e}`);return t},k={getByEntityId:O};var A=k,j=async({router:e,logger:t,entityScopedModelMap:n})=>{!n||!e||Object.entries(n).forEach(([n,r])=>{e.get(`${n}/:id/audit`,async(e,s)=>{try{let{id:a}=e.params,c=await r.findByPk(a);if(!c)return o(new i,s,{logger:t,message:`Entity ${n} with id ${a} not found`});let l=A.getByEntityId(a);return s.json(l)}catch(e){return o(new a(e),s,{logger:t})}})})};let M;const N=e=>{process.env.DISABLE_AUDIT_LOGS!==`true`&&(M=new E(e),j(e),M.registerHooks())},P=(n,r)=>async(i,a,o)=>{try{if(process.env.DISABLE_AUDIT_LOGS===`true`||!M)return o();let s=t(),c=i.headers[m],{performedBy:l,actionOrigin:d}=s?.id?{performedBy:s.id,actionOrigin:u.USER}:{performedBy:c??null,actionOrigin:c?u.AUTOMATION:null},f={entityType:n,action:r,endpoint:i.url,method:i.method,performedBy:l,actionOrigin:d},h=e();h.nonHeaderContext.set(p,f);let g=async()=>{a.off(`finish`,g),a.off(`close`,g),a.off(`error`,g),await M.sendAuditLogContext(f)};x(r)?(a.once(`finish`,g),a.once(`close`,g),a.once(`error`,g)):await M.sendAuditLogContext(f)}catch(e){g.error(`coudln't set audit context`,e)}return o()},F=(t,n)=>async(r,{userId:i,automationId:a})=>{if(process.env.DISABLE_AUDIT_LOGS===`true`||!M)return;let o=e();if(o?.context?.get){let{performedBy:e,actionOrigin:s}=i?{performedBy:i,actionOrigin:u.USER}:{performedBy:a??null,actionOrigin:a?u.AUTOMATION:null},c={entityType:t,action:n,endpoint:r,method:`rabbit`,performedBy:e,actionOrigin:s};o.nonHeaderContext.set(p,c),await M.sendAuditLogContext(c)}};var I=E;export{p as AUDIT_LOG_CONTEXT_KEY,d as AUDIT_LOG_CONTEXT_QUEUE,f as AUDIT_LOG_ROWS_QUEUE,m as AUTOMATION_ID_HEADER,c as Action,u as ActionOrigin,l as EntityType,I as default,N as enableAuditing,P as setAuditContext,F as setRabbitAuditContext};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["logger: LoggerInstanceManager","action: string","fields: string[]","instance: Model","options: CreateOptions<any> | InstanceUpdateOptions<any>","changedFields: string[]","property: string","options: AuditLoggerOptions","payload: AuditLogPayload","context: AuditLogContext","entityId: string","exportObj: { getByEntityId: typeof getByEntityId }","auditLogger: AuditLogger | undefined","options: AuditLoggerOptions","entityType: string","action: string","req: Request","res: Response","next: NextFunction","auditLogContext: AuditLogContext","endpoint: string"],"sources":["../src/types.ts","../src/const.ts","../src/logger.ts","../src/audit-logger.ts","../src/audit-ms.ts","../src/audit-api.ts","../src/index.ts"],"sourcesContent":["/* eslint-disable no-shadow */\nimport type { ModelStatic, Model } from 'sequelize';\nimport type { Sequelize } from 'sequelize-typescript';\nimport type RabbitMq from '@autofleet/rabbit';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport type { IRouter } from 'express';\n\nexport type AuditLoggerOptions = {\n rabbit: RabbitMq;\n sequelize: Sequelize;\n logger: LoggerInstanceManager;\n router?: IRouter;\n entityScopedModelMap?: { [modelName: string]: ModelStatic<Model> };\n};\n\nexport interface AuditLogContext {\n entityType: string;\n entityId?: string;\n action: string;\n performedBy: string;\n endpoint: string;\n method: string;\n actionOrigin?: string;\n}\n\nexport interface AuditLogRow {\n property: string;\n previousValue: any;\n newValue: any;\n}\n\nexport interface AuditLogPayload {\n entityType: string;\n entityId: string;\n rows: AuditLogRow[];\n}\n\nexport enum Action {\n CREATE = 'create',\n BULK_CREATE = 'bulk-create',\n BULK_EDIT = 'bulk-edit',\n DELETE = 'delete',\n UPDATE = 'update',\n CANCEL = 'cancel',\n FAIL = 'fail',\n UNASSIGN = 'unassign',\n BULK_ASSIGN = 'bulk-assign',\n REASSIGN = 'reassign',\n DISPATCH = 'dispatch',\n BULK_DISPATCH = 'bulk-dispatch',\n BULK_UPSERT = 'bulk-upsert',\n UPSERT = 'upsert',\n JOIN = 'join',\n MOVE = 'move',\n REOPTIMIZATION = 'reoptimization',\n}\n\nexport enum EntityType {\n RIDE = 'Ride',\n VEHICLE = 'Vehicle',\n DRIVER = 'Driver',\n PRICE_CALCULATION = 'PriceCalculation',\n}\n\nexport const enum ActionOrigin {\n USER = 'user',\n AUTOMATION = 'automation',\n}\n","export const AUDIT_LOG_CONTEXT_QUEUE = 'audit-log-context';\nexport const AUDIT_LOG_ROWS_QUEUE = 'audit-log-rows';\nexport const AUDIT_LOG_CONTEXT_KEY = 'auditLogContext';\nexport const AUTOMATION_ID_HEADER = 'x-af-automation-id';\n","import Logger, { type LoggerInstanceManager } from '@autofleet/logger';\n\nconst logger: LoggerInstanceManager = Logger(null);\nexport default logger;\n","import type RabbitMq from '@autofleet/rabbit';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport { getCurrentPayload as getCurrentTrace } from '@autofleet/zehut';\nimport type {\n CreateOptions, InstanceUpdateOptions, Model, Sequelize,\n} from 'sequelize';\nimport { isEqual } from 'lodash';\nimport {\n type AuditLogPayload, type AuditLoggerOptions, type AuditLogContext, Action,\n} from './types';\nimport { AUDIT_LOG_CONTEXT_QUEUE, AUDIT_LOG_ROWS_QUEUE, AUDIT_LOG_CONTEXT_KEY } from './const';\nimport logger from './logger';\n\nconst CUSTOM_FIELDS_PROPERTY = 'customFields';\n\nconst getAuditContext = () => {\n const currentTrace = getCurrentTrace();\n const auditContext = currentTrace?.nonHeaderContext?.get(AUDIT_LOG_CONTEXT_KEY);\n return auditContext as AuditLogContext;\n};\n\nconst ACTIONS_TO_OMIT_ENTITY_ID_FROM_CONTEXT = {\n [Action.BULK_CREATE]: Action.BULK_CREATE,\n [Action.BULK_EDIT]: Action.BULK_EDIT,\n [Action.BULK_ASSIGN]: Action.BULK_ASSIGN,\n [Action.BULK_DISPATCH]: Action.BULK_DISPATCH,\n [Action.BULK_UPSERT]: Action.BULK_UPSERT,\n};\n\nconst isEmpty = (field) => {\n if ([null, undefined].includes(field)) {\n return true;\n }\n if (Array.isArray(field)) {\n return field.length === 0;\n }\n if (typeof field === 'object' && !(field instanceof Date)) {\n return Object.keys(field).length === 0;\n }\n return false;\n};\n\nexport const isEntityIdRequired = (action: string): boolean => !ACTIONS_TO_OMIT_ENTITY_ID_FROM_CONTEXT[action];\n\nconst filterOutEmptyFields = (fields: string[], instance) => fields.filter((field) => !isEmpty(instance.get(field)) || !isEmpty(instance.previous(field)));\n\nconst getChangedFieldsRows = (instance: Model, options: CreateOptions<any> | InstanceUpdateOptions<any>) => {\n // When bulk updating in sequelize using the \"returning\" option, the instance.changed() stops working.\n // It's a known issue with sequelize.\n const changedFields: string[] = options.returning ? options.fields as string[] : instance.changed() as string[];\n // Filter customFields - we'll handle them later\n const filteredChangedFields = filterOutEmptyFields(changedFields.filter((field) => field !== CUSTOM_FIELDS_PROPERTY), instance);\n return filteredChangedFields.map((property: string) => ({\n property,\n previousValue: instance.previous(property),\n newValue: instance.get(property),\n }));\n};\n\nconst getChangedCustomFieldsRows = (instance: Model) => {\n const changed = instance.changed();\n const customFieldsChanged = changed && changed.includes(CUSTOM_FIELDS_PROPERTY);\n if (!customFieldsChanged) {\n return [];\n }\n\n const customFields = instance.get(CUSTOM_FIELDS_PROPERTY);\n const previousCustomFields = instance.previous(CUSTOM_FIELDS_PROPERTY);\n const changedCustomFields = Object.keys(customFields).filter((field) => {\n const newValue = customFields?.[field];\n const oldValue = previousCustomFields?.[field];\n return (!isEmpty(newValue) || !isEmpty(oldValue)) && !isEqual(newValue, oldValue);\n });\n return changedCustomFields.map((changedCustomField) => ({\n property: `${CUSTOM_FIELDS_PROPERTY}.${changedCustomField}`,\n previousValue: previousCustomFields?.[changedCustomField],\n newValue: customFields?.[changedCustomField],\n }));\n};\n\nclass AuditLogger {\n private rabbit: RabbitMq;\n\n private sequelize: Sequelize;\n\n private logger: LoggerInstanceManager;\n\n constructor(options: AuditLoggerOptions) {\n this.rabbit = options.rabbit;\n this.sequelize = options.sequelize;\n this.logger = options.logger || logger;\n }\n\n public registerHooks(): void {\n Object.entries(this.sequelize.models).forEach(([modelName, modelType]) => {\n modelType.addHook('afterSave', async (instance, options) => {\n try {\n const auditContext = getAuditContext();\n if (!auditContext) {\n return;\n }\n if (auditContext?.entityType?.toLowerCase() === modelName?.toLowerCase() && isEntityIdRequired(auditContext.action)) {\n auditContext.entityId = (instance as any).id;\n }\n const changedFieldsRows = getChangedFieldsRows(instance, options);\n const changedCustomFieldsRows = getChangedCustomFieldsRows(instance);\n const payload: AuditLogPayload = {\n entityType: modelName,\n entityId: (instance as any).id,\n rows: [...changedFieldsRows, ...changedCustomFieldsRows],\n };\n this.sendAuditLogRows(payload);\n } catch (error) {\n this.logger.error('Failed to send audit log rows', error);\n }\n });\n });\n }\n\n public async sendAuditLogContext(context: AuditLogContext): Promise<void> {\n try {\n await this.rabbit.sendToQueue(AUDIT_LOG_CONTEXT_QUEUE, context);\n } catch (err) {\n this.logger.error('Failed to send audit log context', err);\n }\n }\n\n private async sendAuditLogRows(payload: AuditLogPayload): Promise<void> {\n try {\n await this.rabbit.sendToQueue(AUDIT_LOG_ROWS_QUEUE, payload);\n } catch (err) {\n this.logger.error('Failed to send audit log rows', err);\n }\n }\n}\n\nexport default AuditLogger;\n","import Network from '@autofleet/network';\nimport logger from './logger';\n\nconst auditMs = new Network({ serviceName: 'AUDIT_MS', timeout: 60_000, logger });\n\nconst getByEntityId = async (entityId: string): Promise<any> => {\n const { data } = await auditMs.get(`api/v1/audit-logs/${entityId}`);\n return data;\n};\n\nconst exportObj: { getByEntityId: typeof getByEntityId } = { getByEntityId };\n\nexport default exportObj;\n","import { handleError, ResourceNotFoundError, UnexpectedError } from '@autofleet/errors';\nimport auditMs from './audit-ms';\nimport { AuditLoggerOptions } from './types';\n\nexport default async ({\n router,\n logger,\n entityScopedModelMap,\n}: AuditLoggerOptions): Promise<void> => {\n if (!entityScopedModelMap || !router) {\n return;\n }\n Object.entries(entityScopedModelMap).forEach(([entity, ScopedModel]) => {\n router.get(`${entity}/:id/audit`, async (req, res) => {\n try {\n const { id } = req.params;\n const entityData = await ScopedModel.findByPk(id);\n if (!entityData) {\n return handleError(new ResourceNotFoundError(), res, { logger, message: `Entity ${entity} with id ${id} not found` });\n }\n const auditData = auditMs.getByEntityId(id);\n return res.json(auditData);\n } catch (err) {\n return handleError(new UnexpectedError(err as Error), res, { logger });\n }\n });\n });\n};\n","import { getCurrentPayload as getCurrentTrace, getUser } from '@autofleet/zehut';\nimport type { Request, Response, NextFunction } from 'express';\nimport AuditLogger, { isEntityIdRequired } from './audit-logger';\nimport { ActionOrigin, type AuditLogContext, type AuditLoggerOptions } from './types';\nimport { AUDIT_LOG_CONTEXT_KEY, AUTOMATION_ID_HEADER } from './const';\nimport addAuditApi from './audit-api';\nimport logger from './logger';\n\nlet auditLogger: AuditLogger | undefined;\n\nexport const enableAuditing = (options: AuditLoggerOptions): void => {\n if (process.env.DISABLE_AUDIT_LOGS === 'true') {\n return;\n }\n auditLogger = new AuditLogger(options);\n addAuditApi(options);\n auditLogger.registerHooks();\n};\n\nexport const setAuditContext = (\n entityType: string,\n action: string,\n) => async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n if (process.env.DISABLE_AUDIT_LOGS === 'true' || !auditLogger) {\n return next();\n }\n const user = getUser();\n const automationId = req.headers[AUTOMATION_ID_HEADER] as string | undefined;\n const { performedBy, actionOrigin } = user?.id\n ? { performedBy: user.id, actionOrigin: ActionOrigin.USER }\n : { performedBy: automationId ?? null, actionOrigin: automationId ? ActionOrigin.AUTOMATION : null };\n const auditLogContext: AuditLogContext = {\n entityType,\n action,\n endpoint: req.url,\n method: req.method,\n performedBy,\n actionOrigin,\n };\n\n const currentTrace = getCurrentTrace();\n currentTrace.nonHeaderContext.set(AUDIT_LOG_CONTEXT_KEY, auditLogContext);\n\n const sendAuditLogContextEvent = async () => {\n res.off('finish', sendAuditLogContextEvent);\n res.off('close', sendAuditLogContextEvent);\n res.off('error', sendAuditLogContextEvent);\n await auditLogger.sendAuditLogContext(auditLogContext);\n };\n if (!isEntityIdRequired(action)) { // if it's a bulk action, we don't want to wait for the response to add the entity id\n await auditLogger.sendAuditLogContext(auditLogContext);\n } else {\n res.once('finish', sendAuditLogContextEvent);\n res.once('close', sendAuditLogContextEvent);\n res.once('error', sendAuditLogContextEvent);\n }\n } catch (err) {\n logger.error('coudln\\'t set audit context', err);\n }\n return next();\n};\n\nexport const setRabbitAuditContext = (\n entityType: string,\n action: string,\n) => async (endpoint: string, { userId, automationId }: { userId:string; automationId:string; }): Promise<void> => {\n if (process.env.DISABLE_AUDIT_LOGS === 'true' || !auditLogger) {\n return;\n }\n const currentTrace = getCurrentTrace();\n if (currentTrace?.context?.get) {\n const { performedBy, actionOrigin } = userId\n ? { performedBy: userId, actionOrigin: ActionOrigin.USER }\n : { performedBy: automationId ?? null, actionOrigin: automationId ? ActionOrigin.AUTOMATION : null };\n const auditLogContext: AuditLogContext = {\n entityType,\n action,\n endpoint,\n method: 'rabbit',\n performedBy,\n actionOrigin,\n };\n currentTrace.nonHeaderContext.set(AUDIT_LOG_CONTEXT_KEY, auditLogContext);\n await auditLogger.sendAuditLogContext(auditLogContext);\n }\n};\n\nexport * from './types';\nexport * from './const';\n\nexport default AuditLogger;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["logger: LoggerInstanceManager","action: string","fields: string[]","instance: Model","options: CreateOptions<any> | InstanceUpdateOptions<any>","changedFields: string[]","property: string","options: AuditLoggerOptions","payload: AuditLogPayload","context: AuditLogContext","entityId: string","exportObj: { getByEntityId: typeof getByEntityId }","auditLogger: AuditLogger | undefined","options: AuditLoggerOptions","entityType: string","action: string","req: Request","res: Response","next: NextFunction","auditLogContext: AuditLogContext","endpoint: string"],"sources":["../src/types.ts","../src/const.ts","../src/logger.ts","../src/audit-logger.ts","../src/audit-ms.ts","../src/audit-api.ts","../src/index.ts"],"sourcesContent":["/* eslint-disable no-shadow */\nimport type { ModelStatic, Model } from 'sequelize';\nimport type { Sequelize } from 'sequelize-typescript';\nimport type RabbitMq from '@autofleet/rabbit';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport type { IRouter } from 'express';\n\nexport type AuditLoggerOptions = {\n rabbit: RabbitMq;\n sequelize: Sequelize;\n logger: LoggerInstanceManager;\n router?: IRouter;\n excludeModels?: string[];\n entityScopedModelMap?: { [modelName: string]: ModelStatic<Model> };\n};\n\nexport interface AuditLogContext {\n entityType: string;\n entityId?: string;\n action: string;\n performedBy: string;\n endpoint: string;\n method: string;\n actionOrigin?: string;\n}\n\nexport interface AuditLogRow {\n property: string;\n previousValue: any;\n newValue: any;\n}\n\nexport interface AuditLogPayload {\n entityType: string;\n entityId: string;\n rows: AuditLogRow[];\n}\n\nexport enum Action {\n CREATE = 'create',\n BULK_CREATE = 'bulk-create',\n BULK_EDIT = 'bulk-edit',\n DELETE = 'delete',\n UPDATE = 'update',\n CANCEL = 'cancel',\n FAIL = 'fail',\n UNASSIGN = 'unassign',\n BULK_ASSIGN = 'bulk-assign',\n REASSIGN = 'reassign',\n DISPATCH = 'dispatch',\n BULK_DISPATCH = 'bulk-dispatch',\n BULK_UPSERT = 'bulk-upsert',\n UPSERT = 'upsert',\n JOIN = 'join',\n MOVE = 'move',\n REOPTIMIZATION = 'reoptimization',\n}\n\nexport enum EntityType {\n RIDE = 'Ride',\n VEHICLE = 'Vehicle',\n DRIVER = 'Driver',\n PRICE_CALCULATION = 'PriceCalculation',\n}\n\nexport const enum ActionOrigin {\n USER = 'user',\n AUTOMATION = 'automation',\n}\n","export const AUDIT_LOG_CONTEXT_QUEUE = 'audit-log-context';\nexport const AUDIT_LOG_ROWS_QUEUE = 'audit-log-rows';\nexport const AUDIT_LOG_CONTEXT_KEY = 'auditLogContext';\nexport const AUTOMATION_ID_HEADER = 'x-af-automation-id';\n","import Logger, { type LoggerInstanceManager } from '@autofleet/logger';\n\nconst logger: LoggerInstanceManager = Logger(null);\nexport default logger;\n","import type RabbitMq from '@autofleet/rabbit';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport { getCurrentPayload as getCurrentTrace } from '@autofleet/zehut';\nimport type {\n CreateOptions, InstanceUpdateOptions, Model, Sequelize,\n} from 'sequelize';\nimport { isEqual } from 'lodash';\nimport {\n type AuditLogPayload, type AuditLoggerOptions, type AuditLogContext, Action,\n} from './types';\nimport { AUDIT_LOG_CONTEXT_QUEUE, AUDIT_LOG_ROWS_QUEUE, AUDIT_LOG_CONTEXT_KEY } from './const';\nimport logger from './logger';\n\nconst CUSTOM_FIELDS_PROPERTY = 'customFields';\n\nconst getAuditContext = () => {\n const currentTrace = getCurrentTrace();\n const auditContext = currentTrace?.nonHeaderContext?.get(AUDIT_LOG_CONTEXT_KEY);\n return auditContext as AuditLogContext;\n};\n\nconst ACTIONS_TO_OMIT_ENTITY_ID_FROM_CONTEXT = {\n [Action.BULK_CREATE]: Action.BULK_CREATE,\n [Action.BULK_EDIT]: Action.BULK_EDIT,\n [Action.BULK_ASSIGN]: Action.BULK_ASSIGN,\n [Action.BULK_DISPATCH]: Action.BULK_DISPATCH,\n [Action.BULK_UPSERT]: Action.BULK_UPSERT,\n};\n\nconst isEmpty = (field) => {\n if ([null, undefined].includes(field)) {\n return true;\n }\n if (Array.isArray(field)) {\n return field.length === 0;\n }\n if (typeof field === 'object' && !(field instanceof Date)) {\n return Object.keys(field).length === 0;\n }\n return false;\n};\n\nexport const isEntityIdRequired = (action: string): boolean => !ACTIONS_TO_OMIT_ENTITY_ID_FROM_CONTEXT[action];\n\nconst filterOutEmptyFields = (fields: string[], instance) => fields.filter((field) => !isEmpty(instance.get(field)) || !isEmpty(instance.previous(field)));\n\nconst getChangedFieldsRows = (instance: Model, options: CreateOptions<any> | InstanceUpdateOptions<any>) => {\n // When bulk updating in sequelize using the \"returning\" option, the instance.changed() stops working.\n // It's a known issue with sequelize.\n const changedFields: string[] = options.returning ? options.fields as string[] : instance.changed() as string[];\n // Filter customFields - we'll handle them later\n const filteredChangedFields = filterOutEmptyFields(changedFields.filter((field) => field !== CUSTOM_FIELDS_PROPERTY), instance);\n return filteredChangedFields.map((property: string) => ({\n property,\n previousValue: instance.previous(property),\n newValue: instance.get(property),\n }));\n};\n\nconst getChangedCustomFieldsRows = (instance: Model) => {\n const changed = instance.changed();\n const customFieldsChanged = changed && changed.includes(CUSTOM_FIELDS_PROPERTY);\n if (!customFieldsChanged) {\n return [];\n }\n\n const customFields = instance.get(CUSTOM_FIELDS_PROPERTY);\n const previousCustomFields = instance.previous(CUSTOM_FIELDS_PROPERTY);\n const changedCustomFields = Object.keys(customFields).filter((field) => {\n const newValue = customFields?.[field];\n const oldValue = previousCustomFields?.[field];\n return (!isEmpty(newValue) || !isEmpty(oldValue)) && !isEqual(newValue, oldValue);\n });\n return changedCustomFields.map((changedCustomField) => ({\n property: `${CUSTOM_FIELDS_PROPERTY}.${changedCustomField}`,\n previousValue: previousCustomFields?.[changedCustomField],\n newValue: customFields?.[changedCustomField],\n }));\n};\n\nclass AuditLogger {\n private rabbit: RabbitMq;\n\n private sequelize: Sequelize;\n\n private logger: LoggerInstanceManager;\n\n private excludeModels: string[];\n\n constructor(options: AuditLoggerOptions) {\n this.rabbit = options.rabbit;\n this.sequelize = options.sequelize;\n this.logger = options.logger || logger;\n this.excludeModels = options.excludeModels ?? [];\n }\n\n public registerHooks(): void {\n Object.entries(this.sequelize.models).forEach(([modelName, modelType]) => {\n if (this.excludeModels.includes(modelName)) {\n return;\n }\n modelType.addHook('afterSave', async (instance, options) => {\n try {\n const auditContext = getAuditContext();\n if (!auditContext) {\n return;\n }\n if (auditContext?.entityType?.toLowerCase() === modelName?.toLowerCase() && isEntityIdRequired(auditContext.action)) {\n auditContext.entityId = (instance as any).id;\n }\n const changedFieldsRows = getChangedFieldsRows(instance, options);\n const changedCustomFieldsRows = getChangedCustomFieldsRows(instance);\n const payload: AuditLogPayload = {\n entityType: modelName,\n entityId: (instance as any).id,\n rows: [...changedFieldsRows, ...changedCustomFieldsRows],\n };\n this.sendAuditLogRows(payload);\n } catch (error) {\n this.logger.error('Failed to send audit log rows', error);\n }\n });\n });\n }\n\n public async sendAuditLogContext(context: AuditLogContext): Promise<void> {\n try {\n await this.rabbit.sendToQueue(AUDIT_LOG_CONTEXT_QUEUE, context);\n } catch (err) {\n this.logger.error('Failed to send audit log context', err);\n }\n }\n\n private async sendAuditLogRows(payload: AuditLogPayload): Promise<void> {\n try {\n await this.rabbit.sendToQueue(AUDIT_LOG_ROWS_QUEUE, payload);\n } catch (err) {\n this.logger.error('Failed to send audit log rows', err);\n }\n }\n}\n\nexport default AuditLogger;\n","import Network from '@autofleet/network';\nimport logger from './logger';\n\nconst auditMs = new Network({ serviceName: 'AUDIT_MS', timeout: 60_000, logger });\n\nconst getByEntityId = async (entityId: string): Promise<any> => {\n const { data } = await auditMs.get(`api/v1/audit-logs/${entityId}`);\n return data;\n};\n\nconst exportObj: { getByEntityId: typeof getByEntityId } = { getByEntityId };\n\nexport default exportObj;\n","import { handleError, ResourceNotFoundError, UnexpectedError } from '@autofleet/errors';\nimport auditMs from './audit-ms';\nimport { AuditLoggerOptions } from './types';\n\nexport default async ({\n router,\n logger,\n entityScopedModelMap,\n}: AuditLoggerOptions): Promise<void> => {\n if (!entityScopedModelMap || !router) {\n return;\n }\n Object.entries(entityScopedModelMap).forEach(([entity, ScopedModel]) => {\n router.get(`${entity}/:id/audit`, async (req, res) => {\n try {\n const { id } = req.params;\n const entityData = await ScopedModel.findByPk(id);\n if (!entityData) {\n return handleError(new ResourceNotFoundError(), res, { logger, message: `Entity ${entity} with id ${id} not found` });\n }\n const auditData = auditMs.getByEntityId(id);\n return res.json(auditData);\n } catch (err) {\n return handleError(new UnexpectedError(err as Error), res, { logger });\n }\n });\n });\n};\n","import { getCurrentPayload as getCurrentTrace, getUser } from '@autofleet/zehut';\nimport type { Request, Response, NextFunction } from 'express';\nimport AuditLogger, { isEntityIdRequired } from './audit-logger';\nimport { ActionOrigin, type AuditLogContext, type AuditLoggerOptions } from './types';\nimport { AUDIT_LOG_CONTEXT_KEY, AUTOMATION_ID_HEADER } from './const';\nimport addAuditApi from './audit-api';\nimport logger from './logger';\n\nlet auditLogger: AuditLogger | undefined;\n\nexport const enableAuditing = (options: AuditLoggerOptions): void => {\n if (process.env.DISABLE_AUDIT_LOGS === 'true') {\n return;\n }\n auditLogger = new AuditLogger(options);\n addAuditApi(options);\n auditLogger.registerHooks();\n};\n\nexport const setAuditContext = (\n entityType: string,\n action: string,\n) => async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n if (process.env.DISABLE_AUDIT_LOGS === 'true' || !auditLogger) {\n return next();\n }\n const user = getUser();\n const automationId = req.headers[AUTOMATION_ID_HEADER] as string | undefined;\n const { performedBy, actionOrigin } = user?.id\n ? { performedBy: user.id, actionOrigin: ActionOrigin.USER }\n : { performedBy: automationId ?? null, actionOrigin: automationId ? ActionOrigin.AUTOMATION : null };\n const auditLogContext: AuditLogContext = {\n entityType,\n action,\n endpoint: req.url,\n method: req.method,\n performedBy,\n actionOrigin,\n };\n\n const currentTrace = getCurrentTrace();\n currentTrace.nonHeaderContext.set(AUDIT_LOG_CONTEXT_KEY, auditLogContext);\n\n const sendAuditLogContextEvent = async () => {\n res.off('finish', sendAuditLogContextEvent);\n res.off('close', sendAuditLogContextEvent);\n res.off('error', sendAuditLogContextEvent);\n await auditLogger.sendAuditLogContext(auditLogContext);\n };\n if (!isEntityIdRequired(action)) { // if it's a bulk action, we don't want to wait for the response to add the entity id\n await auditLogger.sendAuditLogContext(auditLogContext);\n } else {\n res.once('finish', sendAuditLogContextEvent);\n res.once('close', sendAuditLogContextEvent);\n res.once('error', sendAuditLogContextEvent);\n }\n } catch (err) {\n logger.error('coudln\\'t set audit context', err);\n }\n return next();\n};\n\nexport const setRabbitAuditContext = (\n entityType: string,\n action: string,\n) => async (endpoint: string, { userId, automationId }: { userId:string; automationId:string; }): Promise<void> => {\n if (process.env.DISABLE_AUDIT_LOGS === 'true' || !auditLogger) {\n return;\n }\n const currentTrace = getCurrentTrace();\n if (currentTrace?.context?.get) {\n const { performedBy, actionOrigin } = userId\n ? { performedBy: userId, actionOrigin: ActionOrigin.USER }\n : { performedBy: automationId ?? null, actionOrigin: automationId ? ActionOrigin.AUTOMATION : null };\n const auditLogContext: AuditLogContext = {\n entityType,\n action,\n endpoint,\n method: 'rabbit',\n performedBy,\n actionOrigin,\n };\n currentTrace.nonHeaderContext.set(AUDIT_LOG_CONTEXT_KEY, auditLogContext);\n await auditLogger.sendAuditLogContext(auditLogContext);\n }\n};\n\nexport * from './types';\nexport * from './const';\n\nexport default AuditLogger;\n"],"mappings":"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"}
|