photo_server_json_flutter_c.../server.js
2026-03-23 12:31:01 +01:00

296 lines
7.4 KiB
JavaScript

// server.js
require('dotenv').config();
const fs = require('fs');
const path = require('path');
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const scanPhoto = require('./api_v1/scanner/scanPhoto.js');
const { WEB_ROOT } = require('./api_v1/config');
const config = require('./api_v1/config');
const db = require('./db/knex');
const photosRouter = require('./routes/photos');
const SECRET_KEY = process.env.JWT_SECRET || '123456789';
const EXPIRES_IN = process.env.JWT_EXPIRES || '1h';
const PORT = process.env.SERVER_PORT || 4000;
const server = express();
// STATIC
server.use(express.static(path.join(__dirname, 'public')));
// BODY PARSER
server.use(express.json());
server.use(express.urlencoded({ extended: true }));
// CONFIG PUBBLICO
server.get('/config', (req, res) => {
res.json({
baseUrl: config.BASE_URL,
pathFull: config.PATH_FULL,
galleryRefreshSeconds: config.GALLERY_REFRESH_SECONDS
});
});
// USERS DB
const userdb = JSON.parse(fs.readFileSync('./api_v1/users.json', 'utf-8'));
// JWT
function createToken(payload) {
return jwt.sign(payload, SECRET_KEY, { expiresIn: EXPIRES_IN });
}
const denylist = new Map();
function addToDenylist(token) {
try {
const decoded = jwt.decode(token);
const exp = decoded?.exp || Math.floor(Date.now() / 1000) + 60;
denylist.set(token, exp);
} catch {
denylist.set(token, Math.floor(Date.now() / 1000) + 60);
}
}
function isRevoked(token) {
const exp = denylist.get(token);
if (!exp) return false;
if (exp * 1000 < Date.now()) {
denylist.delete(token);
return false;
}
return true;
}
function verifyToken(token) {
return jwt.verify(token, SECRET_KEY);
}
// HOME
server.get('/', (req, res) => {
res.sendFile(path.resolve('public/index.html'));
});
// LOGIN
server.post('/auth/login', async (req, res) => {
const { email, password } = req.body;
const user = userdb.users.find((u) => u.email === email);
if (!user) {
return res.status(401).json({ status: 401, message: 'Incorrect email or password' });
}
const ok = await bcrypt.compare(password, user.password);
if (!ok) {
return res.status(401).json({ status: 401, message: 'Incorrect email or password' });
}
const token = createToken({
id: user.id,
email: user.email,
name: user.name,
});
res.status(200).json({
token,
name: user.name,
});
});
// LOGOUT
server.post('/auth/logout', (req, res) => {
const auth = req.headers.authorization || '';
const [scheme, token] = auth.split(' ');
if (scheme === 'Bearer' && token) {
addToDenylist(token);
}
return res.status(204).end();
});
// JWT MIDDLEWARE
server.use(/^(?!\/auth).*$/, (req, res, next) => {
const auth = req.headers.authorization || '';
const [scheme, token] = auth.split(' ');
if (scheme !== 'Bearer' || !token) {
return res.status(401).json({ status: 401, message: 'Bad authorization header' });
}
if (isRevoked(token)) {
return res.status(401).json({ status: 401, message: 'Token revoked' });
}
try {
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (err) {
return res
.status(401)
.json({ status: 401, message: 'Error: access_token is not valid' });
}
});
// FILTRO USER (GET)
server.use((req, res, next) => {
if (req.method === 'GET' && req.user && req.user.name !== 'Admin') {
const u = req.user.name;
const q = req.query.user;
const base = q ? (Array.isArray(q) ? q : [q]) : [];
req.query.user = Array.from(new Set([...base, u, 'Common']));
}
next();
});
// SCAN FOTO
server.get('/scan', async (req, res) => {
try {
if (req.user && req.user.name === 'Admin') {
await scanPhoto(undefined, 'Admin', db);
return res.send({
status: 'Scansione completata',
user: 'Admin',
scope: 'tutti gli utenti + Common',
});
}
await scanPhoto(undefined, req.user.name, db);
res.send({
status: 'Scansione completata',
user: req.user.name,
scope: 'utente corrente',
});
} catch (err) {
console.error('Errore scan:', err);
res.status(500).json({ error: 'Errore durante lo scan', details: err.message });
}
});
// scan automatico
server.post("/api_v1/auto_scan", async (req, res) => {
const { type, file, path, user } = req.body;
console.log("=== SUPER_SCAN EVENT ===");
console.log("TYPE:", type);
console.log("FILE:", file);
console.log("PATH:", path);
console.log("USER:", user);
console.log("========================");
res.json({ ok: true });
});
// FILE STATICI
server.get('/files', (req, res) => {
const requested = req.query.file || '';
const publicDir = path.resolve(path.join(__dirname, 'public'));
const resolved = path.resolve(publicDir, requested);
if (!resolved.startsWith(publicDir)) {
return res.status(400).json({ error: 'Invalid path' });
}
if (!fs.existsSync(resolved) || fs.statSync(resolved).isDirectory()) {
return res.status(404).json({ error: 'Not found' });
}
res.sendFile(resolved);
});
// RESET DB MANUALE
server.get('/initDB', async (req, res) => {
try {
await db('photos').del();
res.json({
status: 'DB resettato',
indexRemoved: false
});
} catch (err) {
console.error('initDB: errore generale:', err);
res.status(500).json({ status: 'errore', message: err.message });
}
});
// DELETE FOTO da DB + thumbs
server.delete('/delphoto/:id', async (req, res) => {
const { id } = req.params;
const trx = await db.transaction();
try {
const createCleanupFunctions = require('./api_v1/scanner/orphanCleanup');
const { deleteThumbsById } = createCleanupFunctions(db);
const deletedThumbs = await deleteThumbsById(id);
const deletedDB = await trx('photos').where({ id }).del();
await trx.commit();
return res.json({
ok: true,
id,
deletedThumbs,
deletedDB: deletedDB > 0,
deletedIndex: false
});
} catch (err) {
await trx.rollback();
console.error('DELPHOTO errore:', err);
return res.status(500).json({ ok: false, error: err.message });
}
});
// RESET DB SOLO PER UN UTENTE
server.get('/initDBuser', async (req, res) => {
try {
let targetUser = req.user.name;
if (req.user.name === 'Admin') {
targetUser = req.query.user;
if (!targetUser) {
return res.status(400).json({
error: "Admin deve specificare ?user=<nome>"
});
}
}
const trx = await db.transaction();
const deleted = await trx('photos').where({ user: targetUser }).del();
await trx.commit();
res.json({
status: "OK",
user: targetUser,
deletedRecords: deleted
});
} catch (err) {
console.error("initDBuser errore:", err);
res.status(500).json({ error: "Errore durante initDBuser", details: err.message });
}
});
// ROUTER PHOTOS (SQLite)
server.use('/photos', photosRouter);
// START SERVER
server.listen(PORT, () => {
console.log(`Auth API server running on port ${PORT} ...`);
});
// Pulizia denylist
setInterval(() => {
const nowSec = Math.floor(Date.now() / 1000);
for (const [tok, exp] of denylist.entries()) {
if (exp < nowSec) denylist.delete(tok);
}
}, 60 * 1000);