Initial push of Plunk Next

This commit is contained in:
Dries Augustyns
2025-12-01 09:56:56 +01:00
parent 07cea20262
commit ff1876d580
566 changed files with 89036 additions and 28423 deletions
+165
View File
@@ -0,0 +1,165 @@
import type {NextFunction, Request, Response} from 'express';
import signale from 'signale';
/**
* Extract request ID from Express response locals
*/
function getRequestId(res?: Response): string | undefined {
return res?.locals?.requestId as string | undefined;
}
/**
* Extract user/project context from Express response locals
*/
function getRequestContext(res?: Response): Record<string, unknown> {
const auth = res?.locals?.auth as {type?: string; userId?: string; projectId?: string} | undefined;
if (!auth) {
return {};
}
return {
authType: auth.type,
userId: auth.userId,
projectId: auth.projectId,
};
}
/**
* Enhanced logger with request ID and context support
*/
export const logger = {
/**
* Log an info message with request context
*/
info(message: string, meta?: Record<string, unknown>, res?: Response) {
const requestId = getRequestId(res);
const context = getRequestContext(res);
signale.info({
prefix: requestId ? `[${requestId}]` : '',
message,
...context,
...meta,
});
},
/**
* Log a warning with request context
*/
warn(message: string, meta?: Record<string, unknown>, res?: Response) {
const requestId = getRequestId(res);
const context = getRequestContext(res);
signale.warn({
prefix: requestId ? `[${requestId}]` : '',
message,
...context,
...meta,
});
},
/**
* Log an error with request context and stack trace
*/
error(message: string, error?: Error | unknown, meta?: Record<string, unknown>, res?: Response) {
const requestId = getRequestId(res);
const context = getRequestContext(res);
const errorDetails = error instanceof Error ? {error: error.message, stack: error.stack} : {error};
signale.error({
prefix: requestId ? `[${requestId}]` : '',
message,
...context,
...errorDetails,
...meta,
});
},
/**
* Log a success message with request context
*/
success(message: string, meta?: Record<string, unknown>, res?: Response) {
const requestId = getRequestId(res);
const context = getRequestContext(res);
signale.success({
prefix: requestId ? `[${requestId}]` : '',
message,
...context,
...meta,
});
},
/**
* Log debug information (only in development)
*/
debug(message: string, meta?: Record<string, unknown>, res?: Response) {
if (process.env.NODE_ENV === 'development') {
const requestId = getRequestId(res);
const context = getRequestContext(res);
signale.debug({
prefix: requestId ? `[${requestId}]` : '',
message,
...context,
...meta,
});
}
},
/**
* Log an API request (called by middleware)
*/
request(req: Request, res: Response) {
const requestId = getRequestId(res);
const context = getRequestContext(res);
signale.info({
prefix: requestId ? `[${requestId}]` : '',
message: `${req.method} ${req.path}`,
...context,
ip: req.ip,
userAgent: req.get('user-agent'),
});
},
/**
* Log an API response (called by middleware)
*/
response(req: Request, res: Response, statusCode: number, duration: number) {
const requestId = getRequestId(res);
const context = getRequestContext(res);
const logFn = statusCode >= 500 ? signale.error : statusCode >= 400 ? signale.warn : signale.success;
logFn({
prefix: requestId ? `[${requestId}]` : '',
message: `${req.method} ${req.path}${statusCode}`,
...context,
statusCode,
duration: `${duration}ms`,
});
},
};
/**
* Express middleware to log all requests and responses
*/
export const requestLogger = (req: Request, res: Response, next: NextFunction) => {
const startTime = Date.now();
// Log incoming request
logger.request(req, res);
// Capture the original res.json to log responses
const originalJson = res.json.bind(res);
res.json = function (body: any) {
const duration = Date.now() - startTime;
logger.response(req, res, res.statusCode, duration);
return originalJson(body);
};
next();
};
+41
View File
@@ -0,0 +1,41 @@
import type {Prisma} from '@plunk/db';
/**
* Shared utility for building Prisma update objects
* Eliminates the repetitive field-by-field update pattern
*/
/**
* Build a Prisma update input object from partial data
* Only includes fields that are defined (not undefined)
*/
export function buildUpdateData<T extends Record<string, unknown>>(
data: Partial<T>,
excludeFields: string[] = [],
): Record<string, unknown> {
const updateData: Record<string, unknown> = {};
for (const [key, value] of Object.entries(data)) {
if (value !== undefined && !excludeFields.includes(key)) {
updateData[key] = value;
}
}
return updateData;
}
/**
* Build email-related fields update data
* Common pattern for Campaign and Template updates
*/
export function buildEmailFieldsUpdate(data: {
name?: string;
description?: string;
subject?: string;
body?: string;
from?: string;
fromName?: string;
replyTo?: string;
}): Prisma.CampaignUpdateInput | Prisma.TemplateUpdateInput {
return buildUpdateData(data) as Prisma.CampaignUpdateInput | Prisma.TemplateUpdateInput;
}