Notification updates

This commit is contained in:
Eric Gullickson
2025-12-21 19:56:52 -06:00
parent 144f1d5bb0
commit 719c80ecd8
80 changed files with 7552 additions and 678 deletions

View File

@@ -17,11 +17,11 @@ export class DocumentsController {
logger.info('Documents list requested', {
operation: 'documents.list',
user_id: userId,
userId,
filters: {
vehicle_id: request.query.vehicleId,
vehicleId: request.query.vehicleId,
type: request.query.type,
expires_before: request.query.expiresBefore,
expiresBefore: request.query.expiresBefore,
},
});
@@ -33,8 +33,8 @@ export class DocumentsController {
logger.info('Documents list retrieved', {
operation: 'documents.list.success',
user_id: userId,
document_count: docs.length,
userId,
documentCount: docs.length,
});
return reply.code(200).send(docs);
@@ -46,26 +46,26 @@ export class DocumentsController {
logger.info('Document get requested', {
operation: 'documents.get',
user_id: userId,
document_id: documentId,
userId,
documentId,
});
const doc = await this.service.getDocument(userId, documentId);
if (!doc) {
logger.warn('Document not found', {
operation: 'documents.get.not_found',
user_id: userId,
document_id: documentId,
userId,
documentId,
});
return reply.code(404).send({ error: 'Not Found' });
}
logger.info('Document retrieved', {
operation: 'documents.get.success',
user_id: userId,
document_id: documentId,
vehicle_id: doc.vehicle_id,
document_type: doc.document_type,
userId,
documentId,
vehicleId: doc.vehicleId,
documentType: doc.documentType,
});
return reply.code(200).send(doc);
@@ -76,9 +76,9 @@ export class DocumentsController {
logger.info('Document create requested', {
operation: 'documents.create',
user_id: userId,
vehicle_id: request.body.vehicle_id,
document_type: request.body.document_type,
userId,
vehicleId: request.body.vehicleId,
documentType: request.body.documentType,
title: request.body.title,
});
@@ -86,10 +86,10 @@ export class DocumentsController {
logger.info('Document created', {
operation: 'documents.create.success',
user_id: userId,
document_id: created.id,
vehicle_id: created.vehicle_id,
document_type: created.document_type,
userId,
documentId: created.id,
vehicleId: created.vehicleId,
documentType: created.documentType,
title: created.title,
});
@@ -102,26 +102,26 @@ export class DocumentsController {
logger.info('Document update requested', {
operation: 'documents.update',
user_id: userId,
document_id: documentId,
update_fields: Object.keys(request.body),
userId,
documentId,
updateFields: Object.keys(request.body),
});
const updated = await this.service.updateDocument(userId, documentId, request.body);
if (!updated) {
logger.warn('Document not found for update', {
operation: 'documents.update.not_found',
user_id: userId,
document_id: documentId,
userId,
documentId,
});
return reply.code(404).send({ error: 'Not Found' });
}
logger.info('Document updated', {
operation: 'documents.update.success',
user_id: userId,
document_id: documentId,
vehicle_id: updated.vehicle_id,
userId,
documentId,
vehicleId: updated.vehicleId,
title: updated.title,
});
@@ -134,28 +134,28 @@ export class DocumentsController {
logger.info('Document delete requested', {
operation: 'documents.delete',
user_id: userId,
document_id: documentId,
userId,
documentId,
});
// If object exists, delete it from storage first
const existing = await this.service.getDocument(userId, documentId);
if (existing && existing.storage_bucket && existing.storage_key) {
if (existing && existing.storageBucket && existing.storageKey) {
const storage = getStorageService();
try {
await storage.deleteObject(existing.storage_bucket, existing.storage_key);
await storage.deleteObject(existing.storageBucket, existing.storageKey);
logger.info('Document file deleted from storage', {
operation: 'documents.delete.storage_cleanup',
user_id: userId,
document_id: documentId,
storage_key: existing.storage_key,
userId,
documentId,
storageKey: existing.storageKey,
});
} catch (e) {
logger.warn('Failed to delete document file from storage', {
operation: 'documents.delete.storage_cleanup_failed',
user_id: userId,
document_id: documentId,
storage_key: existing.storage_key,
userId,
documentId,
storageKey: existing.storageKey,
error: e instanceof Error ? e.message : 'Unknown error',
});
// Non-fatal: proceed with soft delete
@@ -166,10 +166,10 @@ export class DocumentsController {
logger.info('Document deleted', {
operation: 'documents.delete.success',
user_id: userId,
document_id: documentId,
vehicle_id: existing?.vehicle_id,
had_file: !!(existing?.storage_key),
userId,
documentId,
vehicleId: existing?.vehicleId,
hadFile: !!(existing?.storageKey),
});
return reply.code(204).send();
@@ -181,16 +181,16 @@ export class DocumentsController {
logger.info('Document upload requested', {
operation: 'documents.upload',
user_id: userId,
document_id: documentId,
userId,
documentId,
});
const doc = await this.service.getDocument(userId, documentId);
if (!doc) {
logger.warn('Document not found for upload', {
operation: 'documents.upload.not_found',
user_id: userId,
document_id: documentId,
userId,
documentId,
});
return reply.code(404).send({ error: 'Not Found' });
}
@@ -199,8 +199,8 @@ export class DocumentsController {
if (!mp) {
logger.warn('No file provided for upload', {
operation: 'documents.upload.no_file',
user_id: userId,
document_id: documentId,
userId,
documentId,
});
return reply.code(400).send({ error: 'Bad Request', message: 'No file provided' });
}
@@ -216,10 +216,10 @@ export class DocumentsController {
if (!contentType || !allowedTypes.has(contentType)) {
logger.warn('Unsupported file type for upload (header validation)', {
operation: 'documents.upload.unsupported_type',
user_id: userId,
document_id: documentId,
content_type: contentType,
file_name: mp.filename,
userId,
documentId,
contentType,
fileName: mp.filename,
});
return reply.code(415).send({
error: 'Unsupported Media Type',
@@ -248,10 +248,10 @@ export class DocumentsController {
if (!detectedType) {
logger.warn('Unable to detect file type from content', {
operation: 'documents.upload.type_detection_failed',
user_id: userId,
document_id: documentId,
content_type: contentType,
file_name: mp.filename,
userId,
documentId,
contentType,
fileName: mp.filename,
});
return reply.code(415).send({
error: 'Unsupported Media Type',
@@ -264,11 +264,11 @@ export class DocumentsController {
if (!allowedDetectedTypes || !allowedDetectedTypes.has(detectedType.mime)) {
logger.warn('File content does not match Content-Type header', {
operation: 'documents.upload.type_mismatch',
user_id: userId,
document_id: documentId,
claimed_type: contentType,
detected_type: detectedType.mime,
file_name: mp.filename,
userId,
documentId,
claimedType: contentType,
detectedType: detectedType.mime,
fileName: mp.filename,
});
return reply.code(415).send({
error: 'Unsupported Media Type',
@@ -310,29 +310,29 @@ export class DocumentsController {
const bucket = 'documents';
const version = 'v1';
const unique = cryptoRandom();
const key = `documents/${userId}/${doc.vehicle_id}/${doc.id}/${version}/${unique}.${ext}`;
const key = `documents/${userId}/${doc.vehicleId}/${doc.id}/${version}/${unique}.${ext}`;
await storage.putObject(bucket, key, counter, contentType, { 'x-original-filename': originalName });
const updated = await this.service['repo'].updateStorageMeta(doc.id, userId, {
storage_bucket: bucket,
storage_key: key,
file_name: originalName,
content_type: contentType,
file_size: counter.bytes,
file_hash: null,
storageBucket: bucket,
storageKey: key,
fileName: originalName,
contentType: contentType,
fileSize: counter.bytes,
fileHash: null,
});
logger.info('Document upload completed', {
operation: 'documents.upload.success',
user_id: userId,
document_id: documentId,
vehicle_id: doc.vehicle_id,
file_name: originalName,
content_type: contentType,
detected_type: detectedType.mime,
file_size: counter.bytes,
storage_key: key,
userId,
documentId,
vehicleId: doc.vehicleId,
fileName: originalName,
contentType,
detectedType: detectedType.mime,
fileSize: counter.bytes,
storageKey: key,
});
return reply.code(200).send(updated);
@@ -344,18 +344,18 @@ export class DocumentsController {
logger.info('Document download requested', {
operation: 'documents.download',
user_id: userId,
document_id: documentId,
userId,
documentId,
});
const doc = await this.service.getDocument(userId, documentId);
if (!doc || !doc.storage_bucket || !doc.storage_key) {
if (!doc || !doc.storageBucket || !doc.storageKey) {
logger.warn('Document or file not found for download', {
operation: 'documents.download.not_found',
user_id: userId,
document_id: documentId,
has_document: !!doc,
has_storage_info: !!(doc?.storage_bucket && doc?.storage_key),
userId,
documentId,
hasDocument: !!doc,
hasStorageInfo: !!(doc?.storageBucket && doc?.storageKey),
});
return reply.code(404).send({ error: 'Not Found' });
}
@@ -363,10 +363,10 @@ export class DocumentsController {
const storage = getStorageService();
let head: Partial<import('../../../core/storage/storage.service').HeadObjectResult> = {};
try {
head = await storage.headObject(doc.storage_bucket, doc.storage_key);
head = await storage.headObject(doc.storageBucket, doc.storageKey);
} catch { /* ignore */ }
const contentType = head.contentType || doc.content_type || 'application/octet-stream';
const filename = doc.file_name || path.basename(doc.storage_key);
const contentType = head.contentType || doc.contentType || 'application/octet-stream';
const filename = doc.fileName || path.basename(doc.storageKey);
const inlineTypes = new Set(['application/pdf', 'image/jpeg', 'image/png']);
const disposition = inlineTypes.has(contentType) ? 'inline' : 'attachment';
@@ -375,16 +375,16 @@ export class DocumentsController {
logger.info('Document download initiated', {
operation: 'documents.download.success',
user_id: userId,
document_id: documentId,
vehicle_id: doc.vehicle_id,
file_name: filename,
content_type: contentType,
disposition: disposition,
file_size: head.size || doc.file_size,
userId,
documentId,
vehicleId: doc.vehicleId,
fileName: filename,
contentType,
disposition,
fileSize: head.size || doc.fileSize,
});
const stream = await storage.getObjectStream(doc.storage_bucket, doc.storage_key);
const stream = await storage.getObjectStream(doc.storageBucket, doc.storageKey);
return reply.send(stream);
}
}