mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 11:57:08 +00:00
146
lib/www/server/api/middleware/event/import/csv.js
Normal file
146
lib/www/server/api/middleware/event/import/csv.js
Normal file
@@ -0,0 +1,146 @@
|
||||
const Busboy = require('busboy');
|
||||
const { parse } = require('csv-parse/sync');
|
||||
|
||||
async function middleware(req, res, next) {
|
||||
const contentType = req.headers['content-type'] || '';
|
||||
let csvText = null;
|
||||
let filename = null;
|
||||
|
||||
if (req.params.filename && contentType.startsWith('text/csv')) {
|
||||
csvText = typeof req.body === 'string' ? req.body : req.body.toString('utf8');
|
||||
filename = req.params.filename;
|
||||
processCsv();
|
||||
} else if (contentType.startsWith('multipart/form-data')) {
|
||||
const busboy = Busboy({ headers: req.headers });
|
||||
let found = false;
|
||||
busboy.on('file', (name, file, info) => {
|
||||
if (found) {
|
||||
file.resume();
|
||||
return;
|
||||
}
|
||||
if (info.mimeType === 'text/csv') {
|
||||
found = true;
|
||||
filename = info.filename || 'unnamed.csv';
|
||||
csvText = '';
|
||||
file.setEncoding('utf8');
|
||||
file.on('data', (data) => { csvText += data; });
|
||||
file.on('end', () => {});
|
||||
} else {
|
||||
file.resume();
|
||||
}
|
||||
});
|
||||
busboy.on('field', () => {}); // Ignore fields
|
||||
busboy.on('finish', () => {
|
||||
if (!found) {
|
||||
return next();
|
||||
}
|
||||
processCsv();
|
||||
});
|
||||
req.pipe(busboy);
|
||||
return;
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
|
||||
function processCsv() {
|
||||
let records;
|
||||
try {
|
||||
records = parse(csvText, {
|
||||
relax_quotes: true,
|
||||
quote: '"',
|
||||
escape: '"',
|
||||
skip_empty_lines: true,
|
||||
trim: true
|
||||
});
|
||||
} catch (e) {
|
||||
return res.status(400).json({ error: 'Invalid CSV' });
|
||||
}
|
||||
if (!records.length) {
|
||||
return res.status(400).json({ error: 'Empty CSV' });
|
||||
}
|
||||
const headers = records[0].map(h => h.toLowerCase().trim());
|
||||
const rows = records.slice(1);
|
||||
let lastDate = null;
|
||||
let lastTime = null;
|
||||
const currentDate = new Date().toISOString().slice(0, 10);
|
||||
const currentTime = new Date().toISOString().slice(11, 19);
|
||||
const events = [];
|
||||
for (let row of rows) {
|
||||
let object = { labels: [] };
|
||||
for (let k = 0; k < headers.length; k++) {
|
||||
let key = headers[k];
|
||||
let val = row[k] ? row[k].trim() : '';
|
||||
if (!key) continue;
|
||||
if (['remarks', 'event', 'comment', 'comments', 'text'].includes(key)) {
|
||||
object.remarks = val;
|
||||
} else if (key === 'label') {
|
||||
if (val) object.labels.push(val);
|
||||
} else if (key === 'labels') {
|
||||
if (val) object.labels.push(...val.split(';').map(l => l.trim()).filter(l => l));
|
||||
} else if (key === 'sequence' || key === 'seq') {
|
||||
if (val) object.sequence = Number(val);
|
||||
} else if (['point', 'shot', 'shotpoint'].includes(key)) {
|
||||
if (val) object.point = Number(val);
|
||||
} else if (key === 'date') {
|
||||
object.date = val;
|
||||
} else if (key === 'time') {
|
||||
object.time = val;
|
||||
} else if (key === 'timestamp') {
|
||||
object.timestamp = val;
|
||||
} else if (key === 'latitude') {
|
||||
object.latitude = parseFloat(val);
|
||||
} else if (key === 'longitude') {
|
||||
object.longitude = parseFloat(val);
|
||||
}
|
||||
}
|
||||
if (!object.remarks) continue;
|
||||
let useSeqPoint = Number.isFinite(object.sequence) && Number.isFinite(object.point);
|
||||
let tstamp = null;
|
||||
if (!useSeqPoint) {
|
||||
if (object.timestamp) {
|
||||
tstamp = new Date(object.timestamp);
|
||||
}
|
||||
if (!tstamp || isNaN(tstamp.getTime())) {
|
||||
let dateStr = object.date || lastDate || currentDate;
|
||||
let timeStr = object.time || lastTime || currentTime;
|
||||
if (timeStr.length === 5) timeStr += ':00';
|
||||
let full = `${dateStr}T${timeStr}.000Z`;
|
||||
tstamp = new Date(full);
|
||||
if (isNaN(tstamp.getTime())) continue;
|
||||
}
|
||||
if (object.date) lastDate = object.date;
|
||||
if (object.time) lastTime = object.time;
|
||||
}
|
||||
let event = {
|
||||
remarks: object.remarks,
|
||||
labels: object.labels,
|
||||
meta: {
|
||||
author: "*CSVImport*",
|
||||
"*CSVImport*": {
|
||||
filename,
|
||||
tstamp: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!isNaN(object.latitude) && !isNaN(object.longitude)) {
|
||||
event.meta.geometry = {
|
||||
type: "Point",
|
||||
coordinates: [object.longitude, object.latitude]
|
||||
};
|
||||
}
|
||||
if (useSeqPoint) {
|
||||
event.sequence = object.sequence;
|
||||
event.point = object.point;
|
||||
} else if (tstamp) {
|
||||
event.tstamp = tstamp.toISOString();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
events.push(event);
|
||||
}
|
||||
req.body = events;
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = middleware;
|
||||
Reference in New Issue
Block a user