Files
GPI/src/server/controllers/dataSheetController.ts

286 lines
11 KiB
TypeScript

import { Request, Response } from 'express';
import * as dataSheetService from '../services/dataSheetService.js';
import fs from 'fs';
import * as pdfExtractionService from '../services/pdfExtractionService.js';
import { IAppUser } from '../middleware/roleMiddleware.js';
import { notificationService } from '../services/notificationService.js';
interface AuthRequest extends Request {
appUser?: IAppUser;
}
export const getAllDataSheets = async (req: AuthRequest, res: Response) => {
try {
const organizationId = req.appUser?.organizationId;
console.log('Backend: Fetching datasheets for org:', organizationId);
const sheets = await dataSheetService.getAllDataSheets(organizationId);
console.log(`Backend: Found ${sheets.length} sheets`);
res.json(sheets);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error';
res.status(500).json({ error: message });
}
};
export const extractData = async (req: AuthRequest, res: Response) => {
try {
const file = req.file;
if (!file) {
return res.status(400).json({ error: 'File is required' });
}
const fileBuffer = fs.readFileSync(file.path);
const data = await pdfExtractionService.extractDataFromPdf(fileBuffer);
// Return extracted data AND the file path so we don't need to re-upload
res.json({
...data,
tempFilePath: file.path
});
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error';
res.status(500).json({ error: message });
}
};
export const createDataSheet = async (req: AuthRequest, res: Response) => {
try {
const file = req.file;
const {
name, manufacturer, type, solidsVolume, density,
mixingRatio, mixingRatioWeight, mixingRatioVolume,
yieldTheoretical, dftReference, yieldFactor,
wftMin, wftMax, dftMin, dftMax, reducer, dilution,
notes, fileUrl,
manufacturerCode, minStock, typicalApplication
} = req.body;
const organizationId = req.appUser?.organizationId;
// Note: New logic prefers 'file' upload which we store in DB.
// If fileUrl is provided (legacy or external link), we use that but don't store binary.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let fileId: any = undefined;
let finalFileUrl = fileUrl || '';
if (file) {
// Read file buffer
const buffer = fs.readFileSync(file.path);
// Save to StoredFile collection
const { default: StoredFile } = await import('../models/StoredFile.js');
const newFile = await StoredFile.create({
filename: file.originalname,
contentType: file.mimetype,
data: buffer,
size: file.size,
uploadDate: new Date()
});
fileId = newFile._id;
finalFileUrl = newFile._id.toString(); // Use ID as URL reference for consistency with frontend expectations if possible, or we might need to adjust frontend to use /api/datasheets/file/:id
// Clean up temp file
try {
fs.unlinkSync(file.path);
} catch (error) {
console.warn('Failed to delete temp file:', file.path, error);
}
}
if (!fileId && !finalFileUrl) {
// Check if fileUrl allows empty. The schema says optional now, but logically a datasheet usually has a file.
// However, for simplified Diluent registration, we might not have one.
// If the user didn't send a file and didn't send a URL, and schema is optional, we can proceed.
// But let's check if we want to enforce it.
// If manufacturerCode (Diluent indicator?) is present, maybe skip check?
// Actually, I removed 'required' from schema, so I should probably relax this check too.
// return res.status(400).json({ error: 'File is required' });
}
const newSheet = await dataSheetService.createDataSheet({
name,
manufacturer,
manufacturerCode,
type,
minStock: minStock ? Number(minStock) : undefined,
typicalApplication,
fileUrl: finalFileUrl,
fileId: fileId,
solidsVolume: solidsVolume ? Number(solidsVolume) : undefined,
density: density ? Number(density) : undefined,
mixingRatio,
mixingRatioWeight,
mixingRatioVolume,
yieldTheoretical: yieldTheoretical ? Number(yieldTheoretical) : undefined,
dftReference: dftReference ? Number(dftReference) : undefined,
yieldFactor: yieldFactor ? Number(yieldFactor) : undefined,
wftMin: wftMin ? Number(wftMin) : undefined,
wftMax: wftMax ? Number(wftMax) : undefined,
dftMin: dftMin ? Number(dftMin) : undefined,
dftMax: dftMax ? Number(dftMax) : undefined,
reducer,
dilution: dilution ? Number(dilution) : undefined,
notes,
organizationId
});
// Notificação de Nova Ficha Técnica
if (organizationId) {
await notificationService.create({
organizationId,
title: 'Nova Ficha Técnica',
message: `A ficha técnica "${name}" (${manufacturer}) foi adicionada à biblioteca.`,
type: 'info',
metadata: { dataSheetId: newSheet._id, triggerType: 'datasheet_created' }
});
}
res.status(201).json(newSheet);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('Error creating datasheet:', error);
res.status(500).json({ error: message });
}
};
export const deleteDataSheet = async (req: AuthRequest, res: Response) => {
try {
const { id } = req.params;
const organizationId = req.appUser?.organizationId;
// Find sheet to delete file if exists
// (Optional: Implement file deletion logic here if strict cleanup needed)
const success = await dataSheetService.deleteDataSheet(id as string, organizationId);
if (success) {
res.status(204).send();
} else {
res.status(404).json({ error: 'Data sheet not found' });
}
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error';
res.status(500).json({ error: message });
}
};
export const updateDataSheet = async (req: AuthRequest, res: Response) => {
try {
const id = req.params.id as string;
const file = req.file;
const organizationId = req.appUser?.organizationId;
const {
name, manufacturer, type, solidsVolume, density,
mixingRatio, mixingRatioWeight, mixingRatioVolume,
yieldTheoretical, dftReference, yieldFactor,
wftMin, wftMax, dftMin, dftMax, reducer, dilution,
notes, fileUrl
} = req.body;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updates: Record<string, any> = {
name,
manufacturer,
type,
notes,
solidsVolume: solidsVolume ? Number(solidsVolume) : undefined,
density: density ? Number(density) : undefined,
yieldTheoretical: yieldTheoretical ? Number(yieldTheoretical) : undefined,
dftReference: dftReference ? Number(dftReference) : undefined,
yieldFactor: yieldFactor ? Number(yieldFactor) : undefined,
wftMin: wftMin ? Number(wftMin) : undefined,
wftMax: wftMax ? Number(wftMax) : undefined,
dftMin: dftMin ? Number(dftMin) : undefined,
dftMax: dftMax ? Number(dftMax) : undefined,
reducer,
dilution: dilution ? Number(dilution) : undefined,
mixingRatio,
mixingRatioWeight,
mixingRatioVolume
};
if (file) {
// Read file buffer
const buffer = fs.readFileSync(file.path);
// Save to StoredFile collection
const { default: StoredFile } = await import('../models/StoredFile.js');
const newFile = await StoredFile.create({
filename: file.originalname,
contentType: file.mimetype,
data: buffer,
size: file.size,
uploadDate: new Date()
});
updates.fileId = newFile._id;
updates.fileUrl = newFile._id.toString();
// Clean up temp file
try {
fs.unlinkSync(file.path);
} catch (error) {
console.warn('Failed to delete temp file:', file.path, error);
}
} else if (fileUrl) {
updates.fileUrl = String(fileUrl);
// If fileUrl is being updated but not file, we might lose fileId reference?
// If the user sends the same fileUrl (which is the ID), it's fine.
// But if they send a new external URL, we should probably unset fileId.
// For now, let's assume if it's an external URL, fileId should remain unless explicitly cleared?
// Safer: if fileUrl is explicitly sent and doesn't match an ID format, maybe clear fileId?
// Actually, keep it simple.
}
const updatedSheet = await dataSheetService.updateDataSheet(id, updates, organizationId);
if (updatedSheet) {
res.json(updatedSheet);
} else {
res.status(404).json({ error: 'Data sheet not found' });
}
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('Error updating datasheet:', error);
res.status(500).json({ error: message });
}
};
export const getFile = async (req: Request, res: Response) => {
try {
const id_or_filename = req.params.id as string;
// Check if it's a MongoDB ObjectId (24 hex chars)
if (/^[0-9a-fA-F]{24}$/.test(id_or_filename)) {
const { default: StoredFile } = await import('../models/StoredFile.js');
const fileDoc = await StoredFile.findById(id_or_filename);
if (fileDoc) {
res.set('Content-Type', fileDoc.contentType || 'application/pdf');
res.set('Content-Disposition', `inline; filename="${fileDoc.filename}"`);
return res.send(fileDoc.data);
}
}
// Fallback to file system (legacy)
const stream = dataSheetService.getFileStream(id_or_filename);
stream.on('file', (file) => {
res.set('Content-Type', 'application/pdf');
res.set('Content-Disposition', `inline; filename="${file.filename}"`);
});
stream.on('error', () => {
res.status(404).json({ error: 'File not found' });
});
stream.pipe(res);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('Error getting file:', error);
res.status(500).json({ error: message });
}
};