Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d47038f
feat(middleware): actionLogger middleware
JamesTLopez May 31, 2026
a1b2f81
fix: pnpm-workspace push
JamesTLopez May 31, 2026
f22c5c0
feat(routes): add action logger middleware
JamesTLopez May 31, 2026
3f14bef
chore: code cleanup
JamesTLopez May 31, 2026
6bcfce8
fix: eslint fix
JamesTLopez May 31, 2026
383cb4b
chore: simplify comment
JamesTLopez Jun 1, 2026
026b3ed
chore: remove useless utils
JamesTLopez Jun 1, 2026
4bdbece
fix: categoryRouter using hardcoded boolean
JamesTLopez Jun 2, 2026
3d9ffbc
Merge branch 'main' into feat/174-lyric-logging
JamesTLopez Jun 10, 2026
e8f57ab
feat: re-add params/body extraction
JamesTLopez Jun 10, 2026
7e45905
feat: add loggerEnabled config
JamesTLopez Jun 11, 2026
6f9e7a7
feat: update loggerEnabled in worker
JamesTLopez Jun 11, 2026
40c2a68
feat: move logger up
JamesTLopez Jun 11, 2026
4c1fa26
fix: add user to logger and remove duration errormessage
JamesTLopez Jun 14, 2026
b2d4d0d
fix: pass user to formatActionLog
JamesTLopez Jun 14, 2026
0fd47bc
feat: attach requestContext to errorHandler
JamesTLopez Jun 14, 2026
b09bf7a
fix: remove logging from controllers
JamesTLopez Jun 14, 2026
af55190
fix: rename disableLogger
JamesTLopez Jun 14, 2026
5dc7893
fix: remove debug comment
JamesTLopez Jun 14, 2026
d2e21cf
feat: update comment
JamesTLopez Jun 14, 2026
d3aa3ca
Merge branch 'main' into feat/174-lyric-logging
JamesTLopez Jun 30, 2026
37b81d8
Merge branch 'main' into feat/174-lyric-logging
JamesTLopez Jul 20, 2026
ca86a3d
fix: change send to json
JamesTLopez Jul 20, 2026
3067cb9
fix: rename logger config
JamesTLopez Jul 20, 2026
bca1612
fix: remove should log route
JamesTLopez Jul 20, 2026
cd6c961
fix: update naming
JamesTLopez Jul 20, 2026
9890e16
fix: flip enable condition
JamesTLopez Jul 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/data-model/docs/schema.dbml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ table submissions {
dictionary_id integer [not null]
errors jsonb
organization varchar [not null]
published_at timestamp
status submission_status [not null]
created_at timestamp [default: `now()`]
created_by varchar
Expand Down
3 changes: 3 additions & 0 deletions packages/data-provider/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export {
} from './src/middleware/auth.js';
export { errorHandler } from './src/middleware/errorHandler.js';

// middleware
export { actionLoggerMiddleware } from './src/middleware/actionLogger.js';

// routes
export { default as dictionaryRouters } from './src/routers/dictionaryRouter.js';
export { default as migrationRouter } from './src/routers/migrationRouter.js';
Expand Down
2 changes: 2 additions & 0 deletions packages/data-provider/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type SubmissionServiceConfig = {
export type LoggerConfig = {
level?: string;
file?: boolean;
routerLoggerEnabled?: boolean;
};

export type IdServiceConfig = {
Expand Down Expand Up @@ -72,6 +73,7 @@ export interface BaseDependencies {
features?: FeaturesConfig;
idService: IdServiceConfig;
logger: Logger;
routerLoggerEnabled: boolean;
onFinishCommit?: (resultOnCommit: ResultOnCommit) => Promise<void>;
schemaService: SchemaServiceConfig;
submissionService: SubmissionServiceConfig;
Expand Down
5 changes: 1 addition & 4 deletions packages/data-provider/src/controllers/auditController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { AuditPaginatedResponse } from '../utils/types.js';

const controller = (dependencies: BaseDependencies) => {
const auditService = auditSvc(dependencies);
const { logger } = dependencies;
const LOG_MODULE = 'AUDIT_CONTROLLER';

return {
byCategoryIdAndOrganization: validateRequest(auditByCatAndOrgRequestSchema, async (req, res, next) => {
try {
Expand All @@ -23,8 +22,6 @@ const controller = (dependencies: BaseDependencies) => {
// optional query parameters
const { entityName, eventType, startDate, endDate, systemId } = req.query;

logger.info(LOG_MODULE, 'Request Audit', `categoryId '${categoryId}' organization '${organization}'`);

const auditRecords = await auditService.byCategoryIdAndOrganization(categoryId, {
entityName,
eventType,
Expand Down
2 changes: 0 additions & 2 deletions packages/data-provider/src/controllers/categoryController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ const controller = (dependencies: BaseDependencies) => {
try {
const categoryId = Number(req.params.categoryId);

logger.info(LOG_MODULE, 'Request Get Category Details', `categoryId '${categoryId}'`);

const details = await categoryService.getDetails(categoryId);

if (!details) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import { BaseDependencies } from '../config/config.js';
import dictionarySvc from '../services/dictionaryService.js';
import { NotFound } from '../utils/errors.js';
import { validateRequest } from '../utils/requestValidation.js';
import { dictionaryRegisterRequestSchema } from '../utils/schemas.js';
import { downloadDataFileTemplatesSchema } from '../utils/schemas.js';
import { dictionaryRegisterRequestSchema, downloadDataFileTemplatesSchema } from '../utils/schemas.js';
import { RegisterDictionaryResult } from '../utils/types.js';

const controller = (dependencies: BaseDependencies) => {
Expand All @@ -25,11 +24,6 @@ const controller = (dependencies: BaseDependencies) => {
const forceRegistration = req.query.force?.toLowerCase() === 'true';
const user = req.user;

logger.info(
LOG_MODULE,
`Register Dictionary Request categoryName '${categoryName}' name '${dictionaryName}' version '${dictionaryVersion}'`,
);

const { dictionary, category, migrationId } = await dictionaryService.register({
categoryName,
dictionaryName,
Expand Down
47 changes: 0 additions & 47 deletions packages/data-provider/src/controllers/submissionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,13 @@ const controller = ({
}) => {
const submissionService = createSubmissionService(baseDependencies);
const dataService = createSubmittedDataService(baseDependencies);
const { logger } = baseDependencies;
const LOG_MODULE = 'SUBMISSION_CONTROLLER';
return {
commit: validateRequest(submissionCommitRequestSchema, async (req, res, next) => {
try {
const categoryId = Number(req.params.categoryId);
const submissionId = Number(req.params.submissionId);
const user = req.user;

logger.info(LOG_MODULE, `Request Commit Active Submission '${submissionId}' on category '${categoryId}'`);

const submission = await submissionService.getSubmissionById(submissionId);
if (!submission) {
throw new BadRequest(`Submission '${submissionId}' not found`);
Expand All @@ -77,8 +73,6 @@ const controller = ({
const user = req.user;
const force = req.query.force?.toLowerCase() === 'true';

logger.info(LOG_MODULE, `Request Delete Active Submission '${submissionId}'`);

const submission = await submissionService.getSubmissionById(submissionId);
if (!submission) {
throw new BadRequest(`Submission '${submissionId}' not found`);
Expand Down Expand Up @@ -116,11 +110,6 @@ const controller = ({
const index = req.query.index ? parseInt(req.query.index) : null;
const user = req.user;

logger.info(
LOG_MODULE,
`Request Delete '${entityName ? entityName : 'all'}' records on '{${actionType}}' Active Submission '${submissionId}'`,
);

const submission = await submissionService.getSubmissionById(submissionId);
if (!submission) {
throw new BadRequest(`Submission '${submissionId}' not found`);
Expand Down Expand Up @@ -157,8 +146,6 @@ const controller = ({
const systemId = req.params.systemId;
const user = req.user;

logger.info(LOG_MODULE, `Request Delete Submitted Data systemId '${systemId}' on categoryId '${categoryId}'`);

// get SubmittedData by SystemId
const foundRecordToDelete = await dataService.getSubmittedDataBySystemId(categoryId, systemId, {
view: 'flat',
Expand Down Expand Up @@ -192,8 +179,6 @@ const controller = ({
const payload = req.body;
const user = req.user;

logger.info(LOG_MODULE, `Request Edit Submitted Data`);

if (!payload || payload.length == 0) {
throw new BadRequest(
'The "payload" parameter is missing or empty. Please include the records in the request for processing.',
Expand Down Expand Up @@ -231,14 +216,6 @@ const controller = ({
const pageSize = parseInt(String(req.query.pageSize)) || DEFAULT_PAGE_SIZE;
const username = req.query.username;

logger.info(
LOG_MODULE,
`Request Submission categoryId '${categoryId}'`,
`pagination params: page '${page}' pageSize '${pageSize}'`,
`onlyActive '${onlyActive}'`,
`organization '${organization}'`,
);

const submissionsResult = await submissionService.getSubmissionsByCategory(
categoryId,
{ page, pageSize },
Expand All @@ -265,8 +242,6 @@ const controller = ({
try {
const submissionId = Number(req.params.submissionId);

logger.info(LOG_MODULE, `Request Active Submission submissionId '${submissionId}'`);

const submission = await submissionService.getSubmissionById(submissionId);

if (isEmpty(submission)) {
Expand All @@ -289,8 +264,6 @@ const controller = ({
const page = parseInt(String(req.query.page)) || DEFAULT_PAGE;
const pageSize = parseInt(String(req.query.pageSize)) || DEFAULT_PAGE_SIZE;

logger.info(LOG_MODULE, `Request Submission Details by ID '${submissionId}'`);

const submission = await submissionService.getSubmissionDetailsById({
submissionId,
paginationOptions: { page, pageSize },
Expand All @@ -311,11 +284,6 @@ const controller = ({
const categoryId = Number(req.params.categoryId);
const organization = req.params.organization;

logger.info(
LOG_MODULE,
`Request Active Submission categoryId '${categoryId}' and organization '${organization}'`,
);

// Get username from auth
const username = req.user?.username || '';

Expand Down Expand Up @@ -344,13 +312,6 @@ const controller = ({

// TODO: Validate file-entity map in body: no duplicate filenames, and that entities exist in schemaNames

logger.info(
LOG_MODULE,
`Submission Request: categoryId '${categoryId}'`,
` organization '${organization}'`,
` entityName '${entityName}'`,
);

// TODO: parse body payload

if (!payload || !Array.isArray(payload) || payload.length == 0) {
Expand Down Expand Up @@ -392,14 +353,6 @@ const controller = ({
// Get username from auth
const username = req.user?.username || '';

logger.info(
LOG_MODULE,
`Upload Submission Request: categoryId '${categoryId}'`,
` organization '${organization}'`,
` files '${files?.map((f) => f.originalname)}'`,
` fileEntityMap ${JSON.stringify(fileEntityMap)}`,
);

if (!shouldBypassAuth(req, authConfig) && !hasUserWriteAccess(organization, req.user)) {
throw new Forbidden(`User is not authorized to submit data to '${organization}'`);
}
Expand Down
31 changes: 0 additions & 31 deletions packages/data-provider/src/controllers/submittedDataController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,6 @@ const controller = ({
const view = convertToViewType(req.query.view) || defaultView;
const user = req.user;

logger.info(
LOG_MODULE,
`Request Submitted Data on categoryId '${categoryId}'`,
`pagination params: page '${page}' pageSize '${pageSize}'`,
`view '${view}'`,
);

const organizations = getUserReadableOrganizations(user);

const submittedDataResult = await service.getSubmittedDataByCategory(
Expand Down Expand Up @@ -89,13 +82,6 @@ const controller = ({
const view = convertToViewType(String(req.query.view)) || defaultView;
const user = req.user;

logger.info(
LOG_MODULE,
`Request Submitted Data on categoryId '${categoryId}' and organization '${organization}'`,
`pagination params: page '${page}' pageSize '${pageSize}'`,
`view '${view}'`,
);

if (!shouldBypassAuth(req, authConfig) && !hasUserReadAccess(organization, user)) {
throw new Forbidden(`User is not authorized to read submitted data for organization '${organization}'`);
}
Expand Down Expand Up @@ -142,15 +128,6 @@ const controller = ({
const pageSize = parseInt(String(req.query.pageSize)) || DEFAULT_PAGE_SIZE;
const user = req.user;

logger.info(
LOG_MODULE,
'Request Query Submitted Data',
`categoryId '${categoryId}'`,
`organization '${organization}'`,
`sqon '${JSON.stringify(sqon)}'`,
`pagination params: page '${page}' pageSize '${pageSize}'`,
);

if (!shouldBypassAuth(req, authConfig) && !hasUserReadAccess(organization, user)) {
throw new Forbidden(`User is not authorized to read submitted data for organization '${organization}'`);
}
Expand Down Expand Up @@ -191,14 +168,6 @@ const controller = ({
const view = convertToViewType(String(req.query.view)) || defaultView;
const user = req.user;

logger.info(
LOG_MODULE,
'Request Submitted Data',
`categoryId '${categoryId}'`,
`systemId '${systemId}'`,
`params: view '${view}'`,
);

const submittedDataResult = await service.getSubmittedDataBySystemId(categoryId, systemId, {
view,
});
Expand Down
11 changes: 0 additions & 11 deletions packages/data-provider/src/controllers/validationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ const controller = ({
baseDependencies: BaseDependencies;
validatorConfig: ValidatorConfig;
}) => {
const { logger } = baseDependencies;
const LOG_MODULE = 'VALIDATION_CONTROLLER';
const validationSvc = validationService(baseDependencies);

return {
Expand All @@ -22,15 +20,6 @@ const controller = ({
const { categoryId, entityName } = req.params;
const { organization, value } = req.query;

logger.info(
LOG_MODULE,
'Validation Request',
`categoryId '${categoryId}'`,
`entityName '${entityName}'`,
`organization '${organization}'`,
`value '${value}'`,
);

// check if validator is enabled for this category, and entity name
const validatorEntry = findValidatorEntry({ validatorConfig, categoryId, entityName });

Expand Down
1 change: 1 addition & 0 deletions packages/data-provider/src/core/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const provider = (configData: AppConfig, options?: ProviderOptions) => {
features: configData.features,
idService: configData.idService,
logger: getLogger(configData.logger),
routerLoggerEnabled: configData.logger.routerLoggerEnabled ?? true,
schemaService: configData.schemaService,
submissionService: configData.submissionService,
onFinishCommit: configData.onFinishCommit,
Expand Down
40 changes: 40 additions & 0 deletions packages/data-provider/src/middleware/actionLogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { NextFunction, Response } from 'express';

import type { Logger } from '../config/logger.js';
import { ActionResult, extractActionMetadata, formatActionLog } from '../utils/actionLoggerUtils.js';
import type { RequestWithUser } from './auth.js';

export type ActionLoggerConfig = {
enabled: boolean;
};

/**
* Action Logger Middleware
*/
export const actionLoggerMiddleware = (config: ActionLoggerConfig, logger: Logger) => {
return (req: RequestWithUser, res: Response, next: NextFunction) => {
// Skip logging if disabled
if (!config.enabled) {
return next();
}

/**
* Log the action after response is sent
*/
res.on('finish', () => {
const metadata = extractActionMetadata(req);
// Extract metadata now that route has been matched and params are populated
const statusResult = res.statusCode >= 200 && res.statusCode < 400 ? ActionResult.ALLOWED : ActionResult.DENIED;

const logMessage = formatActionLog(metadata, statusResult, res.statusCode, res.locals.user);

if (statusResult === 'DENIED') {
logger.warn(logMessage);
} else {
logger.info(logMessage);
}
});

next();
};
};
2 changes: 2 additions & 0 deletions packages/data-provider/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export const authMiddleware = (authConfig: AuthConfig) => {
const authResult =
typeof authConfig.customAuthHandler === 'function' ? await authConfig.customAuthHandler(req) : {};

// Define locals to access the user result in lyric actionLoggerMiddleware
res.locals.user = authResult.user;
if (authResult.errorCode) {
return res.status(authResult.errorCode).json({ message: authResult.errorMessage });
}
Expand Down
14 changes: 13 additions & 1 deletion packages/data-provider/src/middleware/errorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,17 @@ export const errorHandler = (err: Error, req: Request, res: Response, _next: Nex
status = 500;
}

return res.status(status).send({ error: err.name, message: customizableMsg, details: details });
// Get request context from res.locals (set by validateRequest middleware)
const requestContext = res.locals.requestContext;
if (requestContext) {
req.params = requestContext.params || req.params;
req.query = requestContext.query || req.query;
req.body = requestContext.body || req.body;
}

return res.status(status).send({
error: err.name,
message: customizableMsg,
details: details,
});
};
2 changes: 2 additions & 0 deletions packages/data-provider/src/routers/auditRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { json, Router, urlencoded } from 'express';

import { BaseDependencies } from '../config/config.js';
import auditController from '../controllers/auditController.js';
import { actionLoggerMiddleware } from '../middleware/actionLogger.js';
import { type AuthConfig, authMiddleware } from '../middleware/auth.js';

const router = ({
Expand All @@ -15,6 +16,7 @@ const router = ({
router.use(urlencoded({ extended: false }));
router.use(json());

router.use(actionLoggerMiddleware({ enabled: baseDependencies.routerLoggerEnabled }, baseDependencies.logger));
router.use(authMiddleware(authConfig));

router.get(
Expand Down
Loading