feat: initialize microservice architecture with auth, api, realtime, copernicus, ml, and console modules
This commit is contained in:
73
api/src/index.js
Normal file
73
api/src/index.js
Normal file
@@ -0,0 +1,73 @@
|
||||
const express = require('express');
|
||||
const parser = require('cookie-parser');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT;
|
||||
|
||||
const version = process.env.VERSION;
|
||||
const vBuild = process.env.VERSION_BUILD;
|
||||
const vState = process.env.VERSION_STATE;
|
||||
|
||||
app.use(express.json());
|
||||
app.use(parser());
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.redirect('/health');
|
||||
});
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: "ok",
|
||||
service: "api",
|
||||
version: version,
|
||||
build_number: vBuild,
|
||||
version_state: vState
|
||||
});
|
||||
});
|
||||
|
||||
// Route pubblica: autenticazione tramite SENSOR_CODE (per il plugin)
|
||||
const paramsSensorRoutes = require('./routes/params.sensor');
|
||||
app.use('/params/sensor', paramsSensorRoutes);
|
||||
|
||||
// Middleware di autenticazione per le API
|
||||
app.use((req, res, next) => {
|
||||
if (req.path === '/health' || req.path === '/') return next();
|
||||
|
||||
// 1. Service-to-service: x-api-key header
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
if (apiKey && apiKey === process.env.INTERNAL_API_KEY) {
|
||||
req.internal = true;
|
||||
return next();
|
||||
}
|
||||
|
||||
// 2. User auth: cookie o Authorization header
|
||||
const token = req.cookies?.auth_token
|
||||
|| (req.headers.authorization?.startsWith('Bearer ') && req.headers.authorization.slice(7));
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Unauthorized: Nessun token di autenticazione fornito' });
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });
|
||||
req.user = payload;
|
||||
next();
|
||||
} catch (err) {
|
||||
return res.status(401).json({ error: 'Unauthorized: Token non valido o scaduto' });
|
||||
}
|
||||
});
|
||||
|
||||
const dataRoutes = require('./routes/data');
|
||||
app.use('/data', dataRoutes);
|
||||
|
||||
const storageRoutes = require('./routes/storage')
|
||||
app.use('/storage', storageRoutes)
|
||||
|
||||
const paramsRoutes = require('./routes/params')
|
||||
app.use('/params', paramsRoutes)
|
||||
|
||||
// Avvio del server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Started on port ${PORT}`);
|
||||
});
|
||||
27
api/src/routes/data.js
Normal file
27
api/src/routes/data.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// Collezione di tutte le api che usando influxdb e la telemetria della barca
|
||||
|
||||
//api.mebboat.it/data
|
||||
|
||||
const router = require('express').Router();
|
||||
|
||||
const { write, query, writeBatch } = require('../storage/influx');
|
||||
|
||||
router.post('/write', async (req, res) => {
|
||||
const { measurement, sensor, data } = req.body;
|
||||
await write(measurement, sensor, data);
|
||||
res.json({ status: "ok" });
|
||||
});
|
||||
|
||||
router.post('/write/batch', async (req, res) => {
|
||||
const { points } = req.body;
|
||||
await writeBatch(points);
|
||||
res.json({ result: "added" });
|
||||
});
|
||||
|
||||
router.get('/query', async (req, res) => {
|
||||
const { collection, timeFromNow, measurement, sensor, field } = req.query;
|
||||
const result = await query(collection, timeFromNow, measurement, sensor, field);
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
216
api/src/routes/params.js
Normal file
216
api/src/routes/params.js
Normal file
@@ -0,0 +1,216 @@
|
||||
const router = require('express').Router();
|
||||
const { query } = require('../storage/postgres');
|
||||
|
||||
const sets = ['forecasts', 'marine', 'sensors', 'telemetry'];
|
||||
|
||||
/* ─── READS ─── */
|
||||
|
||||
/**
|
||||
* GET /params/active?set=sensors
|
||||
* Restituisce il set attivo per il tipo richiesto
|
||||
*/
|
||||
router.get('/active', async (req, res) => {
|
||||
const { set } = req.query;
|
||||
if (!set || !sets.includes(set))
|
||||
return res.status(400).json({ error: 'SET parameter invalid' });
|
||||
|
||||
try {
|
||||
const result = await query(`SELECT * FROM ${set} WHERE active = true LIMIT 1`, [], 'references');
|
||||
res.json(result.rows[0] || null);
|
||||
} catch (err) {
|
||||
console.error('[PARAMS] Error reading active set:', err.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /params/sets?type=sensors
|
||||
* Lista tutti i set di un tipo (con paginazione)
|
||||
*/
|
||||
router.get('/sets', async (req, res) => {
|
||||
const { type } = req.query;
|
||||
if (!type || !sets.includes(type))
|
||||
return res.status(400).json({ error: 'type parameter invalid' });
|
||||
|
||||
try {
|
||||
const result = await query(
|
||||
`SELECT id, name, description, mayor, minor, patch, state, active, tags, created_at, updated_at
|
||||
FROM ${type} ORDER BY updated_at DESC`,
|
||||
[], 'references'
|
||||
);
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error('[PARAMS] Error listing sets:', err.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /params/sets/:id?type=sensors
|
||||
* Restituisce un singolo set completo (con content)
|
||||
*/
|
||||
router.get('/sets/:id', async (req, res) => {
|
||||
const { type } = req.query;
|
||||
const { id } = req.params;
|
||||
if (!type || !sets.includes(type))
|
||||
return res.status(400).json({ error: 'type parameter invalid' });
|
||||
|
||||
try {
|
||||
const result = await query(`SELECT * FROM ${type} WHERE id = $1`, [id], 'references');
|
||||
if (!result.rows[0]) return res.status(404).json({ error: 'Set not found' });
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
console.error('[PARAMS] Error reading set:', err.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* ─── WRITES ─── */
|
||||
|
||||
/**
|
||||
* POST /params/sets?type=sensors
|
||||
* Crea un nuovo set di regole
|
||||
*/
|
||||
router.post('/sets', async (req, res) => {
|
||||
const { type } = req.query;
|
||||
if (!type || !sets.includes(type))
|
||||
return res.status(400).json({ error: 'type parameter invalid' });
|
||||
|
||||
const { name, description, content, tags } = req.body;
|
||||
if (!name || !content) return res.status(400).json({ error: 'name and content required' });
|
||||
|
||||
try {
|
||||
const result = await query(
|
||||
`INSERT INTO ${type} (name, description, mayor, minor, patch, state, active, tags, content)
|
||||
VALUES ($1, $2, 1, 0, 0, 'draft', false, $3, $4) RETURNING *`,
|
||||
[name, description || '', tags || [], JSON.stringify(content)],
|
||||
'references'
|
||||
);
|
||||
res.status(201).json(result.rows[0]);
|
||||
} catch (err) {
|
||||
console.error('[PARAMS] Error creating set:', err.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /params/sets/:id?type=sensors
|
||||
* Aggiorna un set esistente
|
||||
*/
|
||||
router.put('/sets/:id', async (req, res) => {
|
||||
const { type } = req.query;
|
||||
const { id } = req.params;
|
||||
if (!type || !sets.includes(type))
|
||||
return res.status(400).json({ error: 'type parameter invalid' });
|
||||
|
||||
const { name, description, content, tags, state } = req.body;
|
||||
|
||||
try {
|
||||
const fields = [];
|
||||
const values = [];
|
||||
let idx = 1;
|
||||
|
||||
if (name !== undefined) { fields.push(`name = $${idx++}`); values.push(name); }
|
||||
if (description !== undefined) { fields.push(`description = $${idx++}`); values.push(description); }
|
||||
if (content !== undefined) { fields.push(`content = $${idx++}`); values.push(JSON.stringify(content)); }
|
||||
if (tags !== undefined) { fields.push(`tags = $${idx++}`); values.push(tags); }
|
||||
if (state !== undefined) { fields.push(`state = $${idx++}`); values.push(state); }
|
||||
|
||||
if (fields.length === 0) return res.status(400).json({ error: 'No fields to update' });
|
||||
|
||||
fields.push(`updated_at = NOW()`);
|
||||
values.push(id);
|
||||
|
||||
const result = await query(
|
||||
`UPDATE ${type} SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
|
||||
values, 'references'
|
||||
);
|
||||
|
||||
if (!result.rows[0]) return res.status(404).json({ error: 'Set not found' });
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
console.error('[PARAMS] Error updating set:', err.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /params/sets/:id/activate?type=sensors
|
||||
* Attiva un set (disattiva tutti gli altri dello stesso tipo)
|
||||
*/
|
||||
router.post('/sets/:id/activate', async (req, res) => {
|
||||
const { type } = req.query;
|
||||
const { id } = req.params;
|
||||
if (!type || !sets.includes(type))
|
||||
return res.status(400).json({ error: 'type parameter invalid' });
|
||||
|
||||
try {
|
||||
// Disattiva tutti
|
||||
await query(`UPDATE ${type} SET active = false WHERE active = true`, [], 'references');
|
||||
// Attiva quello richiesto
|
||||
const result = await query(
|
||||
`UPDATE ${type} SET active = true, state = 'active', updated_at = NOW() WHERE id = $1 RETURNING *`,
|
||||
[id], 'references'
|
||||
);
|
||||
|
||||
if (!result.rows[0]) return res.status(404).json({ error: 'Set not found' });
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
console.error('[PARAMS] Error activating set:', err.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /params/sets/:id?type=sensors
|
||||
* Elimina un set (solo se non attivo)
|
||||
*/
|
||||
router.delete('/sets/:id', async (req, res) => {
|
||||
const { type } = req.query;
|
||||
const { id } = req.params;
|
||||
if (!type || !sets.includes(type))
|
||||
return res.status(400).json({ error: 'type parameter invalid' });
|
||||
|
||||
try {
|
||||
// Controlla che non sia attivo
|
||||
const check = await query(`SELECT active FROM ${type} WHERE id = $1`, [id], 'references');
|
||||
if (!check.rows[0]) return res.status(404).json({ error: 'Set not found' });
|
||||
if (check.rows[0].active) return res.status(409).json({ error: 'Cannot delete an active set' });
|
||||
|
||||
await query(`DELETE FROM ${type} WHERE id = $1`, [id], 'references');
|
||||
res.json({ deleted: true });
|
||||
} catch (err) {
|
||||
console.error('[PARAMS] Error deleting set:', err.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /params/sets/:id/version?type=sensors
|
||||
* Incrementa la versione (bump)
|
||||
*/
|
||||
router.put('/sets/:id/version', async (req, res) => {
|
||||
const { type } = req.query;
|
||||
const { id } = req.params;
|
||||
const { bump } = req.body; // 'major', 'minor', 'patch'
|
||||
if (!type || !sets.includes(type))
|
||||
return res.status(400).json({ error: 'type parameter invalid' });
|
||||
|
||||
const field = bump === 'major' ? 'mayor' : bump === 'minor' ? 'minor' : 'patch';
|
||||
const resets = bump === 'major' ? ', minor = 0, patch = 0' : bump === 'minor' ? ', patch = 0' : '';
|
||||
|
||||
try {
|
||||
const result = await query(
|
||||
`UPDATE ${type} SET ${field} = ${field} + 1 ${resets}, updated_at = NOW() WHERE id = $1 RETURNING *`,
|
||||
[id], 'references'
|
||||
);
|
||||
if (!result.rows[0]) return res.status(404).json({ error: 'Set not found' });
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
console.error('[PARAMS] Error bumping version:', err.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
51
api/src/routes/params.sensor.js
Normal file
51
api/src/routes/params.sensor.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const router = require('express').Router();
|
||||
const crypto = require('crypto');
|
||||
const { query } = require('../storage/postgres');
|
||||
|
||||
const sets = ['forecasts', 'sensors'];
|
||||
|
||||
function hashSensorCode(code) {
|
||||
return crypto.createHash('sha256').update(code).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /params/sensor/:sensorCode/active?set=sensors
|
||||
* Autenticazione tramite SENSOR_CODE (stesso meccanismo di realtime)
|
||||
*/
|
||||
router.get('/:sensorCode/active', async (req, res) => {
|
||||
const { sensorCode } = req.params;
|
||||
const { set } = req.query;
|
||||
|
||||
if (!set || !sets.includes(set))
|
||||
return res.status(400).json({ error: 'SET parameter invalid' });
|
||||
|
||||
try {
|
||||
const hashed = hashSensorCode(sensorCode);
|
||||
const sensor = await query(
|
||||
'SELECT id, is_active FROM sensors WHERE code_hash = $1',
|
||||
[hashed],
|
||||
'sensors'
|
||||
);
|
||||
|
||||
if (!sensor.rows[0]) {
|
||||
return res.status(401).json({ error: 'Sensor code not valid' });
|
||||
}
|
||||
|
||||
if (!sensor.rows[0].is_active) {
|
||||
return res.status(403).json({ error: 'Sensor is not active' });
|
||||
}
|
||||
|
||||
const result = await query(
|
||||
`SELECT * FROM ${set} WHERE active = true LIMIT 1`,
|
||||
[],
|
||||
'references'
|
||||
);
|
||||
|
||||
res.json(result.rows[0] || null);
|
||||
} catch (err) {
|
||||
console.error('[PARAMS/SENSOR] Error:', err.message);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
59
api/src/routes/storage.js
Normal file
59
api/src/routes/storage.js
Normal file
@@ -0,0 +1,59 @@
|
||||
// Collezzione di tutte le api che prendono i dati da minio
|
||||
//api.mebboat.it/storage
|
||||
|
||||
const express = require('express');
|
||||
const { getBuckets, getBucket, getObject, getObjects, getFileStream } = require('../storage/minio');
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* Restituisce una lista con tutti i bucket del database,
|
||||
* altrimenti restituisce i file del bucket passato come parametro
|
||||
*
|
||||
* @param {string} bucket - il bucket
|
||||
*/
|
||||
router.get('/', async (req, res) => {
|
||||
const { bucket } = req.query;
|
||||
|
||||
if (bucket == undefined) {
|
||||
const buckets = await getBuckets();
|
||||
res.status(200).json(buckets);
|
||||
} else {
|
||||
const returnBucket = await getBucket(bucket);
|
||||
res.status(200).json(returnBucket);
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/files', async (req, res) => {
|
||||
const { bucket, fileID } = req.query;
|
||||
|
||||
if (bucket == undefined) {
|
||||
res.status(400).json({ error: "No bucket name in the request" });
|
||||
} else {
|
||||
if (fileID == undefined) {
|
||||
const files = await getObjects(bucket);
|
||||
res.status(200).json(files);
|
||||
} else {
|
||||
const file = await getObject(bucket, fileID);
|
||||
res.status(200).json(file);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/file', async (req, res) => {
|
||||
const { bucket, fileID } = req.query;
|
||||
const stream = await getFileStream(bucket, fileID);
|
||||
res.setHeader('Content-Type', 'application/octet-stream');
|
||||
stream.pipe(res);
|
||||
})
|
||||
|
||||
router.post('/upload', async (req, res) => {
|
||||
const { bucket } = req.query;
|
||||
const files = await getObjects(bucket);
|
||||
res.status(200).json(files);
|
||||
})
|
||||
|
||||
router.get('/download', async (req, res) => {
|
||||
|
||||
})
|
||||
|
||||
module.exports = router;
|
||||
67
api/src/storage/influx.js
Normal file
67
api/src/storage/influx.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const { InfluxDB, Point } = require('@influxdata/influxdb-client');
|
||||
|
||||
const url = process.env.INFLX_URL;
|
||||
const token = process.env.INFLX_TOKEN;
|
||||
const org = process.env.INFLX_ORG;
|
||||
|
||||
const boatTelemetry = "boat"
|
||||
|
||||
const client = new InfluxDB({ url, token })
|
||||
const write = client.getWriteApi(org, boatTelemetry);
|
||||
const querying = client.getQueryApi(org);
|
||||
|
||||
|
||||
async function append(measurement, sensor, data) {
|
||||
const point = new Point(measurement)
|
||||
.tag("sensor", sensor)
|
||||
.floatField('temperature', data.temperature)
|
||||
.floatField('humidity', data.humidity)
|
||||
|
||||
write.writePoint(point);
|
||||
await write.flush();
|
||||
|
||||
}
|
||||
|
||||
async function writeBatch(datas) {
|
||||
datas.forEach(data => {
|
||||
append(data.measurement, data.sensor, data.data);
|
||||
})
|
||||
}
|
||||
|
||||
async function query(bucket, relativeTime, measurement, sensor, field) {
|
||||
|
||||
const fluxTimeRange = relativeTime || "-1h";
|
||||
let fluxQuery = `
|
||||
from(bucket: "${bucket}")
|
||||
|> range(start: ${fluxTimeRange})
|
||||
|> filter(fn: (r) => r._measurement == "${measurement}")`;
|
||||
|
||||
if (sensor) {
|
||||
fluxQuery += `\n |> filter(fn: (r) => r.sensor == "${sensor}")`;
|
||||
}
|
||||
|
||||
if (field) {
|
||||
fluxQuery += `\n |> filter(fn: (r) => r._field == "${field}")`;
|
||||
}
|
||||
|
||||
fluxQuery += `\n |> yield(name: "data")`;
|
||||
|
||||
try {
|
||||
const data = [];
|
||||
for await (const { values, tableMeta } of querying.iterateRows(fluxQuery)) {
|
||||
data.push(tableMeta.toObject(values));
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error in query:", error);
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
write:append,
|
||||
writeBatch,
|
||||
query
|
||||
}
|
||||
|
||||
136
api/src/storage/minio.js
Normal file
136
api/src/storage/minio.js
Normal file
@@ -0,0 +1,136 @@
|
||||
const Minio = require('minio');
|
||||
|
||||
const client = new Minio.Client({
|
||||
endPoint: process.env.MINIO_ENDPOINT || 'localhost',
|
||||
port: parseInt(process.env.MINIO_PORT) || 9000,
|
||||
useSSL: process.env.MINIO_USE_SSL === 'true',
|
||||
accessKey: process.env.MINIO_ACCESS_KEY,
|
||||
secretKey: process.env.MINIO_SECRET_KEY
|
||||
})
|
||||
|
||||
|
||||
// Buckets
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} bucket - Il nome del bucket
|
||||
* @returns {String} - restituisce il nome del bucket creato
|
||||
*/
|
||||
async function bucketExists(bucket) {
|
||||
const exists = await client.bucketExists(bucket);
|
||||
if(!exists) {
|
||||
await client.makeBucket(bucket);
|
||||
}
|
||||
return bucket
|
||||
}
|
||||
|
||||
/**
|
||||
* Restituisce un array con tutti i bucket sul server
|
||||
* @returns {Array} - i diversi bucket presenti sul server
|
||||
*/
|
||||
async function getBuckets() {
|
||||
const buckets = await client.listBuckets();
|
||||
return buckets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restituisce i metadata del bucket passato come parametro
|
||||
* @param {String} bucket - il nome del bucket
|
||||
* @returns {Object} - i metadata del bucket
|
||||
*/
|
||||
async function getBucket(bucket) {
|
||||
const buckets = await client.listBuckets();
|
||||
return buckets.filter(b => b.name === bucket);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restituisce un array con tutti gli oggetti presenti nel bucket passato come parametro
|
||||
* @param {String} bucket - il nome del bucket
|
||||
* @returns {Promise<Array>} - i file del bucket
|
||||
*/
|
||||
async function getObjects(bucket) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const objects = [];
|
||||
const stream = client.listObjects(bucket, '', true);
|
||||
stream.on('data', obj => objects.push(obj));
|
||||
stream.on('error', err => reject(err));
|
||||
stream.on('end', () => resolve(objects));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restituisce i metadata del file con id passato come parametro presente nel bucket
|
||||
* @param {String} bucket - il nome del bucket
|
||||
* @param {String} objectName - il nome dell'oggetto
|
||||
* @returns {Object} - i metadata del file
|
||||
*/
|
||||
async function getObject(bucket, objectName) {
|
||||
const item = await client.statObject(bucket, objectName);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina il file con l'id passato a parametro dal bucket
|
||||
* @param {String} bucket - il nome del bucket
|
||||
* @param {String} objectName - il nome dell'oggetto
|
||||
*/
|
||||
async function removeObject(bucket, objectName) {
|
||||
await client.removeObject(bucket, objectName);
|
||||
}
|
||||
|
||||
|
||||
//Upload - Download
|
||||
/**
|
||||
* Carica un file nel bucket come buffer
|
||||
* @param {String} bucket - il nome del bucket
|
||||
* @param {String} objectName - il nome che avrà il file in Minio
|
||||
* @param {Buffer} fileBuffer - il file caricato
|
||||
* @param {Number} size - dimensione del file
|
||||
* @param {String} contentType - mimetype (es. 'image/png')
|
||||
*/
|
||||
async function upload(bucket, objectName, fileBuffer, size, contentType) {
|
||||
await bucketExists(bucket);
|
||||
|
||||
const metaData = {
|
||||
'Content-Type': contentType || 'application/octet-stream',
|
||||
}
|
||||
|
||||
const result = await client.putObject(bucket, objectName, fileBuffer, size, metaData);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera un URL temporaneo di download
|
||||
* @param {String} bucket - il nome del bucket
|
||||
* @param {String} objectName - il nome del file
|
||||
* @param {Number} expiry - quanto dura il link in secondi (default: 24h = 86400)
|
||||
*
|
||||
* @returns {String} url - url di download del file
|
||||
*/
|
||||
async function download(bucket, objectName, expiry = 86400) {
|
||||
const url = await client.presignedGetObject(bucket, objectName, expiry);
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recupera e ritorna lo stream dei dati dal server Minio (per leggere il contenuto via API)
|
||||
* @param {String} bucket - il nome del bucket
|
||||
* @param {String} objectName - il nome dell'oggetto
|
||||
*/
|
||||
async function getFileStream(bucket, objectName) {
|
||||
const dataStream = await client.getObject(bucket, objectName);
|
||||
return dataStream;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
bucketExists,
|
||||
getBuckets,
|
||||
getBucket,
|
||||
getObjects,
|
||||
getObject,
|
||||
removeObject,
|
||||
upload,
|
||||
download,
|
||||
getFileStream
|
||||
}
|
||||
79
api/src/storage/postgres.js
Normal file
79
api/src/storage/postgres.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const { Pool } = require('pg');
|
||||
|
||||
const config = {
|
||||
user: process.env.PG_USER,
|
||||
password: process.env.PG_PASSWORD,
|
||||
host: process.env.PG_HOST,
|
||||
port: process.env.PG_PORT,
|
||||
max: 10,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 5000
|
||||
}
|
||||
|
||||
const pools = {
|
||||
users: new Pool({ ...config, database: process.env.DATA_DB }),
|
||||
references: new Pool({ ...config, database: process.env.REFERENCES_DB }),
|
||||
sensors: new Pool({ ...config, database: process.env.SENSOR_DB || 'users' })
|
||||
}
|
||||
|
||||
Object.entries(pools).forEach(([name, pool]) => {
|
||||
pool.on('error', (err) => {
|
||||
console.error(`Error in ${name} pool`, err);
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {'users' | 'references'} db - the name of the database
|
||||
* @returns {Promise<import('pg').PoolClient>}
|
||||
*/
|
||||
async function getClient(db) {
|
||||
const pool = pools[db];
|
||||
if (!pool) throw new Error(`Database pool type ${db} does not exist`);
|
||||
return await pool.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Esegue una query sul database specificato
|
||||
* @param {string} text - Query SQL
|
||||
* @param {any[]} params - Parametri
|
||||
* @param {'users' | 'references'} name - Quale DB usare
|
||||
*/
|
||||
async function query(text, params, name = 'users') {
|
||||
const client = await getClient(name);
|
||||
try {
|
||||
return await client.query(text, params);
|
||||
} catch (error) {
|
||||
console.error(`[DB Query Error on ${name}]`, error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserisce una riga in una tabella
|
||||
*/
|
||||
async function append(table, data, type = 'users') {
|
||||
const keys = Object.keys(data);
|
||||
const values = Object.values(data);
|
||||
const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');
|
||||
const columns = keys.join(', ');
|
||||
const sql = `INSERT INTO ${table} (${columns}) VALUES (${placeholders}) RETURNING *`;
|
||||
return await query(sql, values, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rimuove una riga
|
||||
*/
|
||||
async function remove(table, condition, params, type = 'users') {
|
||||
const sql = `DELETE FROM ${table} WHERE ${condition}`;
|
||||
return await query(sql, params, type);
|
||||
}
|
||||
module.exports = {
|
||||
query,
|
||||
append,
|
||||
remove,
|
||||
getClient,
|
||||
pools
|
||||
};
|
||||
Reference in New Issue
Block a user