first commit

This commit is contained in:
Fabio 2026-02-22 22:25:00 +01:00
commit 35cf1e641c
924 changed files with 11533 additions and 0 deletions

6
.env Normal file
View file

@ -0,0 +1,6 @@
BASE_URL=https://prova.patachina.it
SERVER_PORT=4000
EMAIL=fabio@gmail.com
PASSWORD=master66
JWT_SECRET=123456789
JWT_EXPIRES=1h

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules/

View file

@ -0,0 +1,25 @@
/**
* Required libraries
*/
const faker = require('faker')
var productsDatabase = { products: [], sellers: [] }
for (var i = 1; i <= 10; i++) {
productsDatabase.products.push({
id: i,
name: faker.commerce.product(),
color: faker.commerce.color(),
cost: faker.commerce.price(),
quantity: Math.floor(Math.random() * 1000)
})
productsDatabase.sellers.push({
id: i,
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
jobArea: faker.name.jobArea(),
address: faker.address.country()
})
}
console.log(JSON.stringify(productsDatabase))

108
api_v1/altri/photo.js Normal file
View file

@ -0,0 +1,108 @@
const fs = require('fs');
const path = require('path');
const ExifReader = require('exifreader');
const sharp = require('sharp');
var productsDatabase = { photos: []}
var i = 1;
var s = 0;
//console.log("start search");
async function searchFile(dir, fileExt) {
s++;
// read the contents of the directory
const files = fs.readdirSync(dir);
// search through the files
for (let k = 0; k < files.length; k++) {
file = files[k];
const filePath = path.join(dir, file);
// get the file stats
const fileStat = fs.statSync(filePath);
// if the file is a directory, recursively search the directory
if (fileStat.isDirectory()) {
await searchFile(filePath, fileExt);
} else if (path.extname(file).toLowerCase() == fileExt) {
// if the file is a match, print it
var dd = dir.split("/");
var d = dd.slice(1);
var d1 = dd.slice(1);
d1[1]= "thumbs";
var ff = file.split(".");
var fn = ff.slice(0,-1).join(".");
var f1 = fn+'_min'+'.'+ff.at(-1);
var f2 = fn+'_avg'+'.'+ff.at(-1);
var f3 = fn+'_sharp'+'.'+ff.at(-1);
var extFilePath = path.join(d.join("/"),file);
var extThumbMinPath = path.join(d1.join("/"),f1);
var extThumbAvgPath = path.join(d1.join("/"),f2);
var extThumbSharpPath = path.join("public",d1.join("/"),f3);
var thumbMinPath = path.join("public",extThumbMinPath);
var thumbAvgPath = path.join("public",extThumbAvgPath);
var dt = path.join("public",d1.join("/"));
if (!fs.existsSync(dt)){
fs.mkdirSync(dt, { recursive: true });
}
var data;
try {
data = fs.readFileSync(filePath);
} catch (err) {
//console.error(err);
}
const tags = await ExifReader.load(filePath, {expanded: true});
//console.log(tags);
var time = tags.exif.DateTimeOriginal;
if (time === undefined){} else {time=time.value[0]}
var gps = tags['gps'];
//if (time === undefined ){console.log(filePath)}
//console.log("read: "+filePath);
await sharp(data)
.resize(100,100,"inside")
.withMetadata()
.toFile(thumbMinPath, (err, info) => {});
await sharp(data)
.resize(400)
.withMetadata()
.toFile(thumbAvgPath, (err, info) => {});
//console.log(pub);
productsDatabase.photos.push({
id: i,
name: file,
path: extFilePath,
thub1: extThumbMinPath,
thub2: extThumbAvgPath,
gps: tags['gps'],
data: time
});
i++;
}
if(k == files.length-1) {
if (s == 1) {
fs.writeFileSync("api_v1/db.json", JSON.stringify(productsDatabase));
//console.log("finito");
//console.log(productsDatabase);
} else {
s--;
}
}
}
}
async function thumb(filePath, opt){
try {
const thumbnail = await imageThumbnail(filePath, opt);
//console.log(thumbnail);
return thumbnail;
} catch (err) {
//console.error(err);
}
}
// start the search in the current directory
async function run(){
await searchFile('./public/photos/original', '.jpg');
//console.log(JSON.stringify(productsDatabase))
}
run()

108
api_v1/altri/photo1.js Normal file
View file

@ -0,0 +1,108 @@
const fs = require('fs');
const path = require('path');
const ExifReader = require('exifreader');
const sharp = require('sharp');
var productsDatabase = { photos: []}
var i = 1;
var s = 0;
//console.log("start search");
async function searchFile(dir, fileExt) {
s++;
// read the contents of the directory
const files = fs.readdirSync(dir);
// search through the files
for (let k = 0; k < files.length; k++) {
file = files[k];
const filePath = path.join(dir, file);
// get the file stats
const fileStat = fs.statSync(filePath);
// if the file is a directory, recursively search the directory
if (fileStat.isDirectory()) {
await searchFile(filePath, fileExt);
} else if (path.extname(file).toLowerCase() == fileExt) {
// if the file is a match, print it
var dd = dir.split("/");
var d = dd.slice(1);
var d1 = dd.slice(1);
d1[1]= "thumbs";
var ff = file.split(".");
var fn = ff.slice(0,-1).join(".");
var f1 = fn+'_min'+'.'+ff.at(-1);
var f2 = fn+'_avg'+'.'+ff.at(-1);
var f3 = fn+'_sharp'+'.'+ff.at(-1);
var extFilePath = path.join(d.join("/"),file);
var extThumbMinPath = path.join(d1.join("/"),f1);
var extThumbAvgPath = path.join(d1.join("/"),f2);
var extThumbSharpPath = path.join("public",d1.join("/"),f3);
var thumbMinPath = path.join("public",extThumbMinPath);
var thumbAvgPath = path.join("public",extThumbAvgPath);
var dt = path.join("public",d1.join("/"));
if (!fs.existsSync(dt)){
fs.mkdirSync(dt, { recursive: true });
}
var data;
try {
data = fs.readFileSync(filePath);
} catch (err) {
//console.error(err);
}
const tags = await ExifReader.load(filePath, {expanded: true});
//console.log(tags);
var time = tags.exif.DateTimeOriginal;
if (time === undefined){} else {time=time.value[0]}
var gps = tags['gps'];
//if (time === undefined ){console.log(filePath)}
//console.log("read: "+filePath);
await sharp(data)
.resize(100,100,"inside")
.withMetadata()
.toFile(thumbMinPath, (err, info) => {});
await sharp(data)
.resize(400)
.withMetadata()
.toFile(thumbAvgPath, (err, info) => {});
console.log(i+" - "+file);
productsDatabase.photos.push({
id: i,
name: file,
path: extFilePath,
thub1: extThumbMinPath,
thub2: extThumbAvgPath,
gps: tags['gps'],
data: time
});
i++;
}
if(k == files.length-1) {
if (s == 1) {
fs.writeFileSync("api_v1/db.json", JSON.stringify(productsDatabase));
//console.log("finito");
//console.log(productsDatabase);
} else {
s--;
}
}
}
}
async function thumb(filePath, opt){
try {
const thumbnail = await imageThumbnail(filePath, opt);
//console.log(thumbnail);
return thumbnail;
} catch (err) {
//console.error(err);
}
}
// start the search in the current directory
async function run(){
await searchFile('./public/photos/original', '.jpg');
//console.log(JSON.stringify(productsDatabase))
}
run()

173
api_v1/altri/scanphoto1.js Normal file
View file

@ -0,0 +1,173 @@
const fs = require('fs');
const path = require('path');
const ExifReader = require('exifreader');
const sharp = require('sharp');
const axios = require('axios');
const loc = require('./geo.js');
var productsDatabase = { photos: []}
var i = 1;
var s = 0;
//console.log("start search");
async function searchFile(dir, fileExt) {
//azzera();
s++;
// read the contents of the directory
const files = fs.readdirSync(dir);
// search through the files
for (let k = 0; k < files.length; k++) {
file = files[k];
const filePath = path.join(dir, file);
// get the file stats
const fileStat = fs.statSync(filePath);
// if the file is a directory, recursively search the directory
if (fileStat.isDirectory()) {
await searchFile(filePath, fileExt);
} else if (path.extname(file).toLowerCase() == fileExt) {
// if the file is a match, print it
var dd = dir.split("/");
var d = dd.slice(1);
var d1 = dd.slice(1);
d1[1]= "thumbs";
var ff = file.split(".");
var fn = ff.slice(0,-1).join(".");
var f1 = fn+'_min'+'.'+ff.at(-1);
var f2 = fn+'_avg'+'.'+ff.at(-1);
var f3 = fn+'_sharp'+'.'+ff.at(-1);
var extFilePath = path.join(d.join("/"),file);
var extThumbMinPath = path.join(d1.join("/"),f1);
var extThumbAvgPath = path.join(d1.join("/"),f2);
var extThumbSharpPath = path.join("public",d1.join("/"),f3);
var thumbMinPath = path.join("public",extThumbMinPath);
var thumbAvgPath = path.join("public",extThumbAvgPath);
var dt = path.join("public",d1.join("/"));
if (!fs.existsSync(dt)){
fs.mkdirSync(dt, { recursive: true });
}
var data;
try {
data = fs.readFileSync(filePath);
} catch (err) {
//console.error(err);
}
const tags = await ExifReader.load(filePath, {expanded: true});
//console.log(tags);
var time = tags.exif.DateTimeOriginal;
if (time === undefined){} else {time=time.value[0]}
var gps = tags['gps'];
//console.log(gps.Latitude);
//console.log(gps.latitude);
//var loc;
var locat;
//console.log("ora");
if (gps === undefined){} else {
// locat = await loc(gps.Longitude,gps.Latitude);
}
//if (time === undefined ){console.log(filePath)}
//console.log("read: "+filePath);
await sharp(data)
.resize(100,100,"inside")
.withMetadata()
.toFile(thumbMinPath, (err, info) => {});
await sharp(data)
.resize(400)
.withMetadata()
.toFile(thumbAvgPath, (err, info) => {});
console.log(i+" - "+file);
productsDatabase.photos.push({
id: i,
name: file,
path: extFilePath,
thub1: extThumbMinPath,
thub2: extThumbAvgPath,
gps: tags['gps'],
data: time
});
i++;
}
if(k == files.length-1) {
if (s == 1) {
fs.writeFileSync('api_v1/db.json', JSON.stringify(productsDatabase));
//console.log("finito1");
//console.log(productsDatabase);
} else {
s--;
}
}
}
}
async function thumb(filePath, opt){
try {
const thumbnail = await imageThumbnail(filePath, opt);
//console.log(thumbnail);
return thumbnail;
} catch (err) {
//console.error(err);
}
}
// start the search in the current directory
async function scanPhoto(dir){
await searchFile(dir, '.jpg');
//console.log("finito2");
}
function scrivi(json) {
fetch('http://192.168.1.3:7771/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({'email':'fabio@gmail.com', 'password':'master66'}),
})
.then(response => response.json())
.then(user1 => {
const myHeaders = new Headers();
myHeaders.append('Authorization', 'Bearer ' + user1.token);
myHeaders.append('Content-Type', 'application/json');
//console.log(myHeaders.get("Content-Type"));
//console.log(myHeaders.get("Authorization"));
fetch('http://192.168.1.3:7771/photos', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(json),
})
.then(response => response.json())
//.then(user => console.log("caricato"));
});
}
function azzera() {
fetch('http://192.168.1.3:7771/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({'email':'fabio@gmail.com', 'password':'master66'}),
})
.then(response => response.json())
.then(user1 => {
const myHeaders = new Headers();
myHeaders.append('Authorization', 'Bearer ' + user1.token);
myHeaders.append('Content-Type', 'application/json');
//console.log(myHeaders.get("Content-Type"));
//console.log(myHeaders.get("Authorization"));
fetch('http://192.168.1.3:7771/photos', {
method: 'POST',
headers: myHeaders,
body: "",
})
.then(response => response.json())
.then(user => console.log("azzerato totalmente"));
});
}
module.exports = scanPhoto;

2907
api_v1/db.json Normal file

File diff suppressed because it is too large Load diff

96
api_v1/geo.js Normal file
View file

@ -0,0 +1,96 @@
const axios = require("axios");
// Funzione principale
async function loc(lng, lat) {
const primary = await place(lng, lat); // Geoapify
const fallback = await placePhoton(lng, lat); // Photon
// Se Geoapify fallisce → usa Photon
if (!primary) return fallback;
// Se Geoapify manca city → prendi da Photon
if (!primary.city && fallback?.city) {
primary.city = fallback.city;
}
// Se Geoapify manca postcode → prendi da Photon
if (!primary.postcode && fallback?.postcode) {
primary.postcode = fallback.postcode;
}
// Se Geoapify manca address → prendi da Photon
if (!primary.address && fallback?.address) {
primary.address = fallback.address;
}
// Se Geoapify manca region → prendi da Photon
if (!primary.region && fallback?.region) {
primary.region = fallback.region;
}
// Se Geoapify manca county_code → Photon NON lo fornisce
// quindi non possiamo riempirlo
return primary;
}
// Geoapify (sorgente principale)
async function place(lng, lat) {
const apiKey = "6dc7fb95a3b246cfa0f3bcef5ce9ed9a";
const url = `https://api.geoapify.com/v1/geocode/reverse?lat=${lat}&lon=${lng}&apiKey=${apiKey}`;
try {
const r = await axios.get(url);
if (r.status !== 200) return undefined;
if (!r.data.features || r.data.features.length === 0) return undefined;
const k = r.data.features[0].properties;
return {
continent: k?.timezone?.name?.split("/")?.[0] || undefined,
country: k?.country || undefined,
region: k?.state || undefined,
postcode: k?.postcode || undefined,
city: k?.city || undefined,
county_code: k?.county_code || undefined,
address: k?.address_line1 || undefined,
timezone: k?.timezone?.name || undefined,
time: k?.timezone?.offset_STD || undefined
};
} catch (err) {
return undefined;
}
}
// Photon (fallback)
async function placePhoton(lng, lat) {
try {
const url = `https://photon.patachina.it/reverse?lon=${lng}&lat=${lat}`;
const r = await axios.get(url);
if (!r.data || !r.data.features || r.data.features.length === 0) {
return undefined;
}
const p = r.data.features[0].properties;
return {
continent: undefined, // Photon non lo fornisce
country: p.country || undefined,
region: p.state || undefined,
postcode: p.postcode || undefined,
city: p.city || p.town || p.village || undefined,
county_code: undefined, // Photon non fornisce codici ISO
address: p.street ? `${p.street} ${p.housenumber || ""}`.trim() : undefined,
timezone: undefined,
time: undefined
};
} catch (err) {
return undefined;
}
}
module.exports = loc;

55
api_v1/geo.js.old Normal file
View file

@ -0,0 +1,55 @@
const axios = require('axios');
async function loc(lng,lat){
const l = await place(lng,lat);
if(l === undefined){
return await place1(lng,lat);
} else {
if(l.county_code === undefined){
const l1 = await place1(lng,lat);
if(l1 === undefined){ } else {
l.county_code = l1.province_code;
}
}
return l;
}
}
async function place(lng,lat) {
const myAPIKey = "6dc7fb95a3b246cfa0f3bcef5ce9ed9a";
const reverseGeocodingUrl = `https://api.geoapify.com/v1/geocode/reverse?lat=${lat}&lon=${lng}&apiKey=${myAPIKey}`
//console.log(reverseGeocodingUrl);
try {
const f = await axios.get(reverseGeocodingUrl);
if(f.status == 200){
const k = f.data.features[0].properties;
return {continent: k.timezone.name.split("/")[0],country: k.country, region: k.state, postcode: k.postcode, city: k.city, county_code: k.county_code, address: k.address_line1, timezone: k.timezone.name, time: k.timezone.offset_STD }
} else {
//console.log("errore1a");
return undefined;
}
} catch (error) {
//console.log("errore1b");
return undefined;
}
}
async function place1(lng,lat){
try {
const loc = await axios.get('http://192.168.1.3:6565/query?'+lng+'&'+lat);
const k = loc.data
//console.log(loc);
return {continent: k.region,country: k.country, region: undefined, postcode: undefined, city: undefined, county_code: k.province_code.split("-")[1], address: undefined, timezone: undefined, time: undefined };
} catch (error) {
//console.log("errore2");
return undefined;
}
}
module.exports = loc;

75
api_v1/geo.js.old2 Normal file
View file

@ -0,0 +1,75 @@
const axios = require('axios');
async function loc(lng, lat) {
const primary = await place(lng, lat);
if (!primary) {
return await place1(lng, lat);
}
if (!primary.county_code) {
const fallback = await place1(lng, lat);
if (fallback && fallback.county_code) {
primary.county_code = fallback.county_code;
}
}
return primary;
}
async function place(lng, lat) {
const apiKey = "6dc7fb95a3b246cfa0f3bcef5ce9ed9a";
const url = `https://api.geoapify.com/v1/geocode/reverse?lat=${lat}&lon=${lng}&apiKey=${apiKey}`;
try {
const r = await axios.get(url);
if (r.status !== 200) return undefined;
if (!r.data.features || r.data.features.length === 0) return undefined;
const k = r.data.features[0].properties;
return {
continent: k?.timezone?.name?.split("/")?.[0] || undefined,
country: k?.country || undefined,
region: k?.state || undefined,
postcode: k?.postcode || undefined,
city: k?.city || undefined,
county_code: k?.county_code || undefined,
address: k?.address_line1 || undefined,
timezone: k?.timezone?.name || undefined,
time: k?.timezone?.offset_STD || undefined
};
} catch (err) {
return undefined;
}
}
async function place1(lng, lat) {
try {
const r = await axios.get(`http://192.168.1.3:6565/query?${lng}&${lat}`);
const k = r.data;
const county = k?.province_code?.includes("-")
? k.province_code.split("-")[1]
: k?.province_code;
return {
continent: k?.region || undefined,
country: k?.country || undefined,
region: undefined,
postcode: undefined,
city: undefined,
county_code: county || undefined,
address: undefined,
timezone: undefined,
time: undefined
};
} catch (err) {
return undefined;
}
}
module.exports = loc;

15
api_v1/geo/georev.js Normal file
View file

@ -0,0 +1,15 @@
const axios = require('axios');
axios.get('http://192.168.1.3:6565/query?12.082991&44.250747')
.then(res => {
const headerDate = res.headers && res.headers.date ? res.headers.date : 'no response date';
console.log('Status Code:', res.status);
console.log('Date in Response header:', headerDate);
console.log(res.data);
})
.catch(err => {
console.log('Error: ', err.message);
});

16
api_v1/geo/georev1.js Normal file
View file

@ -0,0 +1,16 @@
const axios = require('axios');
console.log("richiesta loc");
run();
async function run(){
try {
loc = await axios.get('http://192.168.1.3:6565/query?-9.437794444444444&52.96421388888889');
console.log(loc.data);
} catch (error) {
loc = "errore";// Handle errors
console.log(loc);
}
}

60
api_v1/geo/georev2.js Normal file
View file

@ -0,0 +1,60 @@
const axios = require('axios');
run();
async function run(){
var a = await loc(12.082978,44.250746);
console.log(a);
}
async function loc(lng,lat){
const l = await place(lng,lat);
if(l === undefined){
return await place1(lng,lat);
} else {
if(l.county_code === undefined){
const l1 = await place1(lng,lat);
if(l1 === undefined){ } else {
l.county_code = l1.province_code.split("-")[1];
}
}
return l;
}
}
async function place(lng,lat) {
const myAPIKey = "6dc7fb95a3b246cfa0f3bcef5ce9ed9a";
const reverseGeocodingUrl = `https://api.geoapify1.com/v1/geocode/reverse?lat=${lat}&lon=${lng}&apiKey=${myAPIKey}`
//console.log(reverseGeocodingUrl);
try {
const f = await axios.get(reverseGeocodingUrl);
if(f.status == 200){
const k = f.data.features[0].properties;
return {continent: k.timezone.name.split("/")[0],country: k.country, region: k.state, postcode: k.postcode, city: k.city, county_code: k.county_code, address: k.address_line1, timezone: k.timezone.name, time: k.timezone.offset_STD }
} else {
//console.log("errore1a");
return undefined;
}
} catch (error) {
//console.log("errore1b");
return undefined;
}
}
async function place1(lng,lat){
try {
const loc = await axios.get('http://192.168.1.3:6565/query?'+lng+'&'+lat);
const k = loc.data
//console.log(loc);
return {continent: k.region,country: k.country, region: undefined, postcode: undefined, city: undefined, county_code: k.province_code.split("-")[1], address: undefined, timezone: undefined, time: undefined };
} catch (error) {
//console.log("errore2");
return undefined;
}
}

39
api_v1/geo/georev3.js Normal file
View file

@ -0,0 +1,39 @@
const axios = require('axios');
run();
async function run(){
var a = await loc(12.082978,44.250746);
console.log(a);
}
async function loc(lng,lat){
const l = await place(lng,lat);
return l;
}
async function place(lng,lat) {
const reverseGeocodingUrl = 'https://nominatim.openstreetmap.org/reverse?lat='+lat+'&lon='+lng+'&format=jsonv2';
//console.log(reverseGeocodingUrl);
try {
const f = await axios.get(reverseGeocodingUrl);
if(f.status == 200){
const k = f.data.address;
//return {continent: k.timezone.name.split("/")[0],country: k.country, region: k.state, postcode: k.postcode, city: k.city, county_code: k.county_code, address: k.address_line1, timezone: k.timezone.name, time: k.timezone.offset_STD }
return k;
} else {
//console.log("errore1a");
return undefined;
}
} catch (error) {
//console.log("errore1b");
return undefined;
}
}

13
api_v1/index mmm.html Normal file
View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="it">
<head>
</head>
<body>
<script>
window.addEventListener("load", (event) => {
window.location.href = "pub/index.html";
});
</script>
</body>
</html>

1
api_v1/initialDB.json Normal file
View file

@ -0,0 +1 @@
{"photos":[]}

380
api_v1/scanphoto.js Normal file
View file

@ -0,0 +1,380 @@
/* eslint-disable no-console */
require('dotenv').config();
const fs = require('fs');
const fsp = require('fs/promises');
const path = require('path');
const crypto = require('crypto');
const ExifReader = require('exifreader');
const sharp = require('sharp');
const axios = require('axios');
const { exec } = require('child_process');
// IMPORT GEO.JS
const loc = require('./geo.js');
const BASE_URL = process.env.BASE_URL;
const EMAIL = process.env.EMAIL;
const PASSWORD = process.env.PASSWORD;
const SEND_PHOTOS = (process.env.SEND_PHOTOS || 'true').toLowerCase() === 'true';
const WRITE_INDEX = (process.env.WRITE_INDEX || 'true').toLowerCase() === 'true';
const WEB_ROOT = process.env.WEB_ROOT || 'public';
const INDEX_PATH = process.env.INDEX_PATH || path.posix.join('photos', 'index.json');
const SUPPORTED_EXTS = new Set([
'.jpg', '.jpeg', '.png', '.webp', '.heic', '.heif',
'.mp4', '.mov', '.m4v'
]);
// -----------------------------------------------------------------------------
// UTILS
// -----------------------------------------------------------------------------
function toPosix(p) {
return p.split(path.sep).join('/');
}
function sha256(s) {
return crypto.createHash('sha256').update(s).digest('hex');
}
function inferMimeFromExt(ext) {
switch (ext.toLowerCase()) {
case '.jpg':
case '.jpeg': return 'image/jpeg';
case '.png': return 'image/png';
case '.webp': return 'image/webp';
case '.heic':
case '.heif': return 'image/heic';
case '.mp4': return 'video/mp4';
case '.mov': return 'video/quicktime';
case '.m4v': return 'video/x-m4v';
default: return 'application/octet-stream';
}
}
function parseExifDateUtc(s) {
if (!s) return null;
const re = /^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
const m = re.exec(s);
if (!m) return null;
const dt = new Date(Date.UTC(+m[1], +m[2]-1, +m[3], +m[4], +m[5], +m[6]));
return dt.toISOString();
}
// -----------------------------------------------------------------------------
// GPS — FOTO
// -----------------------------------------------------------------------------
function extractGpsFromExif(tags) {
if (!tags?.gps) return null;
const lat = tags.gps.Latitude;
const lng = tags.gps.Longitude;
const alt = tags.gps.Altitude;
if (lat == null || lng == null) return null;
return {
lat: Number(lat),
lng: Number(lng),
alt: alt != null ? Number(alt) : null
};
}
// -----------------------------------------------------------------------------
// GPS — VIDEO (exiftool)
// -----------------------------------------------------------------------------
function extractGpsWithExiftool(videoPath) {
return new Promise((resolve) => {
const cmd = `exiftool -n -G1 -a -gps:all -quicktime:all -user:all "${videoPath}"`;
exec(cmd, (err, stdout) => {
if (err || !stdout) return resolve(null);
const userData = stdout.match(/GPS Coordinates\s*:\s*([0-9\.\-]+)\s+([0-9\.\-]+)/i);
if (userData) {
return resolve({
lat: Number(userData[1]),
lng: Number(userData[2]),
alt: null
});
}
const lat1 = stdout.match(/GPSLatitude\s*:\s*([0-9\.\-]+)/i);
const lng1 = stdout.match(/GPSLongitude\s*:\s*([0-9\.\-]+)/i);
if (lat1 && lng1) {
return resolve({
lat: Number(lat1[1]),
lng: Number(lng1[1]),
alt: null
});
}
const coords = stdout.match(/GPSCoordinates\s*:\s*([0-9\.\-]+)\s+([0-9\.\-]+)/i);
if (coords) {
return resolve({
lat: Number(coords[1]),
lng: Number(coords[2]),
alt: null
});
}
resolve(null);
});
});
}
// -----------------------------------------------------------------------------
// AUTH / POST
// -----------------------------------------------------------------------------
let cachedToken = null;
async function getToken(force = false) {
if (!SEND_PHOTOS) return null;
if (cachedToken && !force) return cachedToken;
try {
const res = await axios.post(`${BASE_URL}/auth/login`, {
email: EMAIL,
password: PASSWORD
});
cachedToken = res.data.token;
return cachedToken;
} catch (err) {
console.error('ERRORE LOGIN:', err.message);
return null;
}
}
async function postWithAuth(url, payload) {
if (!SEND_PHOTOS) return;
let token = await getToken();
if (!token) throw new Error('Token assente');
try {
await axios.post(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
timeout: 20000,
});
} catch (err) {
if (err.response && err.response.status === 401) {
token = await getToken(true);
if (!token) throw err;
await axios.post(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
timeout: 20000,
});
} else {
throw err;
}
}
}
// -----------------------------------------------------------------------------
// VIDEO: ffmpeg thumbnail
// -----------------------------------------------------------------------------
function createVideoThumbnail(videoPath, thumbMinPath, thumbAvgPath) {
return new Promise((resolve) => {
const cmd = `
ffmpeg -y -i "${videoPath}" -ss 00:00:01.000 -vframes 1 "${thumbAvgPath}" &&
ffmpeg -y -i "${thumbAvgPath}" -vf "scale=100:-1" "${thumbMinPath}"
`;
exec(cmd, () => resolve());
});
}
// -----------------------------------------------------------------------------
// VIDEO: ffprobe metadata
// -----------------------------------------------------------------------------
function probeVideo(videoPath) {
return new Promise((resolve) => {
const cmd = `ffprobe -v quiet -print_format json -show_format -show_streams "${videoPath}"`;
exec(cmd, (err, stdout) => {
if (err) return resolve({});
try {
resolve(JSON.parse(stdout));
} catch {
resolve({});
}
});
});
}
// -----------------------------------------------------------------------------
// THUMBNAILS IMMAGINI
// -----------------------------------------------------------------------------
async function createThumbnails(filePath, thumbMinPath, thumbAvgPath) {
try {
await sharp(filePath)
.resize({ width: 100, height: 100, fit: 'inside', withoutEnlargement: true })
.withMetadata()
.toFile(thumbMinPath);
await sharp(filePath)
.resize({ width: 400, withoutEnlargement: true })
.withMetadata()
.toFile(thumbAvgPath);
} catch (err) {
console.error('Errore creazione thumbnails:', err.message, filePath);
}
}
// -----------------------------------------------------------------------------
// SCANSIONE RICORSIVA
// -----------------------------------------------------------------------------
async function scanDir(dirAbs, results = []) {
const dirEntries = await fsp.readdir(dirAbs, { withFileTypes: true });
for (const dirent of dirEntries) {
const absPath = path.join(dirAbs, dirent.name);
if (dirent.isDirectory()) {
await scanDir(absPath, results);
continue;
}
const ext = path.extname(dirent.name).toLowerCase();
if (!SUPPORTED_EXTS.has(ext)) continue;
const isVideo = ['.mp4', '.mov', '.m4v'].includes(ext);
const relFile = toPosix(path.relative(WEB_ROOT, absPath));
const relDir = toPosix(path.posix.dirname(relFile));
const relThumbDir = relDir.replace(/original/i, 'thumbs');
const absThumbDir = path.join(WEB_ROOT, relThumbDir);
await fsp.mkdir(absThumbDir, { recursive: true });
const baseName = path.parse(dirent.name).name;
const absThumbMin = path.join(absThumbDir, `${baseName}_min.jpg`);
const absThumbAvg = path.join(absThumbDir, `${baseName}_avg.jpg`);
if (isVideo) {
await createVideoThumbnail(absPath, absThumbMin, absThumbAvg);
} else {
await createThumbnails(absPath, absThumbMin, absThumbAvg);
}
const relThumbMin = toPosix(path.relative(WEB_ROOT, absThumbMin));
const relThumbAvg = toPosix(path.relative(WEB_ROOT, absThumbAvg));
let tags = {};
try {
tags = await ExifReader.load(absPath, { expanded: true });
} catch {}
const timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null;
const takenAtIso = parseExifDateUtc(timeRaw);
let gps = null;
if (isVideo) {
gps = await extractGpsWithExiftool(absPath);
} else {
gps = extractGpsFromExif(tags);
}
let width = null, height = null, size_bytes = null, duration = null;
const st = await fsp.stat(absPath);
size_bytes = st.size;
if (isVideo) {
const info = await probeVideo(absPath);
const stream = info.streams?.find(s => s.width && s.height);
if (stream) {
width = stream.width;
height = stream.height;
}
duration = info.format?.duration || null;
} else {
try {
const meta = await sharp(absPath).metadata();
width = meta.width || null;
height = meta.height || null;
} catch {}
}
const mime_type = inferMimeFromExt(ext);
const id = sha256(relFile);
// GEOLOCATION
const location = gps ? await loc(gps.lng, gps.lat) : null;
results.push({
id,
name: dirent.name,
path: relFile,
thub1: relThumbMin,
thub2: relThumbAvg,
gps,
data: timeRaw,
taken_at: takenAtIso,
mime_type,
width,
height,
size_bytes,
duration: isVideo ? duration : null,
location
});
}
return results;
}
// -----------------------------------------------------------------------------
// MAIN
// -----------------------------------------------------------------------------
async function scanPhoto(dir) {
try {
const absDir = path.isAbsolute(dir) ? dir : path.join(process.cwd(), dir);
const photos = await scanDir(absDir);
if (SEND_PHOTOS && BASE_URL) {
for (const p of photos) {
try {
await postWithAuth(`${BASE_URL}/photos`, p);
} catch (err) {
console.error('Errore invio:', err.message);
}
}
}
if (WRITE_INDEX) {
const absIndexPath = path.join(WEB_ROOT, INDEX_PATH);
await fsp.mkdir(path.dirname(absIndexPath), { recursive: true });
await fsp.writeFile(absIndexPath, JSON.stringify(photos, null, 2), 'utf8');
}
return photos;
} catch (e) {
console.error('Errore generale scanPhoto:', e);
throw e;
}
}
module.exports = scanPhoto;

273
api_v1/scanphoto.js.ok Normal file
View file

@ -0,0 +1,273 @@
/* eslint-disable no-console */
require('dotenv').config();
const fs = require('fs');
const fsp = require('fs/promises');
const path = require('path');
const crypto = require('crypto');
const ExifReader = require('exifreader');
const sharp = require('sharp');
const axios = require('axios');
const BASE_URL = process.env.BASE_URL; // es: https://api.tuoserver.tld (backend con /auth/login e /photos)
const EMAIL = process.env.EMAIL;
const PASSWORD = process.env.PASSWORD;
// Opzioni
const SEND_PHOTOS = (process.env.SEND_PHOTOS || 'true').toLowerCase() === 'true'; // invia ogni record via POST /photos
const WRITE_INDEX = (process.env.WRITE_INDEX || 'true').toLowerCase() === 'true'; // scrivi public/photos/index.json
const WEB_ROOT = process.env.WEB_ROOT || 'public'; // radice dei file serviti
const INDEX_PATH = process.env.INDEX_PATH || path.posix.join('photos', 'index.json');
// estensioni supportate
const SUPPORTED_EXTS = new Set(['.jpg', '.jpeg', '.png', '.webp', '.heic', '.heif']);
// -----------------------------------------------------------------------------
// UTILS
// -----------------------------------------------------------------------------
// usa sempre POSIX per i path web (slash '/')
function toPosix(p) {
return p.split(path.sep).join('/');
}
function sha256(s) {
return crypto.createHash('sha256').update(s).digest('hex');
}
function inferMimeFromExt(ext) {
switch (ext.toLowerCase()) {
case '.jpg':
case '.jpeg': return 'image/jpeg';
case '.png': return 'image/png';
case '.webp': return 'image/webp';
case '.heic':
case '.heif': return 'image/heic';
default: return 'application/octet-stream';
}
}
// EXIF "YYYY:MM:DD HH:mm:ss" -> ISO-8601 UTC
function parseExifDateUtc(s) {
if (!s) return null;
const re = /^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
const m = re.exec(s);
if (!m) return null;
const dt = new Date(Date.UTC(+m[1], +m[2]-1, +m[3], +m[4], +m[5], +m[6]));
return dt.toISOString();
}
// normalizza GPS da {Latitude,Longitude,Altitude} -> {lat,lng,alt}
function mapGps(gps) {
if (!gps) return null;
const lat = gps.Latitude;
const lng = gps.Longitude;
const alt = gps.Altitude;
if (lat == null || lng == null) return null;
const toNum = (v) => (typeof v === 'number' ? v : Number(v));
const obj = { lat: toNum(lat), lng: toNum(lng) };
if (alt != null) obj.alt = toNum(alt);
return obj;
}
// -----------------------------------------------------------------------------
// AUTH / POST
// -----------------------------------------------------------------------------
let cachedToken = null;
async function getToken(force = false) {
if (!SEND_PHOTOS) return null;
if (cachedToken && !force) return cachedToken;
try {
const res = await axios.post(`${BASE_URL}/auth/login`, { email: EMAIL, password: PASSWORD });
cachedToken = res.data.token;
return cachedToken;
} catch (err) {
console.error('ERRORE LOGIN:', err.message);
return null;
}
}
async function postWithAuth(url, payload) {
if (!SEND_PHOTOS) return;
let token = await getToken();
if (!token) throw new Error('Token assente');
try {
await axios.post(url, payload, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
timeout: 20000,
});
} catch (err) {
if (err.response && err.response.status === 401) {
token = await getToken(true);
if (!token) throw err;
await axios.post(url, payload, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
timeout: 20000,
});
} else {
throw err;
}
}
}
// -----------------------------------------------------------------------------
// THUMBNAILS
// -----------------------------------------------------------------------------
async function createThumbnails(filePath, thumbMinPath, thumbAvgPath) {
try {
await sharp(filePath)
.resize({ width: 100, height: 100, fit: 'inside', withoutEnlargement: true })
.withMetadata()
.toFile(thumbMinPath);
await sharp(filePath)
.resize({ width: 400, withoutEnlargement: true })
.withMetadata()
.toFile(thumbAvgPath);
} catch (err) {
console.error('Errore creazione thumbnails:', err.message, filePath);
}
}
// -----------------------------------------------------------------------------
// SCANSIONE RICORSIVA (asincrona)
// -----------------------------------------------------------------------------
async function scanDir(dirAbs, results = []) {
const dirEntries = await fsp.readdir(dirAbs, { withFileTypes: true });
for (const dirent of dirEntries) {
const absPath = path.join(dirAbs, dirent.name);
if (dirent.isDirectory()) {
await scanDir(absPath, results);
continue;
}
const ext = path.extname(dirent.name).toLowerCase();
if (!SUPPORTED_EXTS.has(ext)) continue;
console.log('Trovato:', absPath);
// path relativo (web-safe) rispetto a WEB_ROOT
const relFile = toPosix(path.relative(WEB_ROOT, absPath)); // es. photos/original/.../IMG_0092.JPG
const relDir = toPosix(path.posix.dirname(relFile)); // es. photos/original/... (POSIX)
// cartella thumbs parallela
const relThumbDir = relDir.replace(/original/i, 'thumbs');
const absThumbDir = path.join(WEB_ROOT, relThumbDir);
await fsp.mkdir(absThumbDir, { recursive: true });
const baseName = path.parse(dirent.name).name;
const extName = path.parse(dirent.name).ext;
// path ASSOLUTI (filesystem) per i file thumb
const absThumbMin = path.join(absThumbDir, `${baseName}_min${extName}`);
const absThumbAvg = path.join(absThumbDir, `${baseName}_avg${extName}`);
// crea thumbnails
await createThumbnails(absPath, absThumbMin, absThumbAvg);
// path RELATIVI (web) dei thumb
const relThumbMin = toPosix(path.relative(WEB_ROOT, absThumbMin)); // photos/thumbs/..._min.JPG
const relThumbAvg = toPosix(path.relative(WEB_ROOT, absThumbAvg)); // photos/thumbs/..._avg.JPG
// EXIF e metadata immagine
let tags = {};
try {
tags = await ExifReader.load(absPath, { expanded: true });
} catch (err) {
// nessun EXIF: ok
}
const timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null;
const takenAtIso = parseExifDateUtc(timeRaw);
const gps = mapGps(tags?.gps);
// dimensioni e peso
let width = null, height = null, size_bytes = null;
try {
const meta = await sharp(absPath).metadata();
width = meta.width || null;
height = meta.height || null;
const st = await fsp.stat(absPath);
size_bytes = st.size;
} catch (err) {
try {
const st = await fsp.stat(absPath);
size_bytes = st.size;
} catch {}
}
const mime_type = inferMimeFromExt(ext);
const id = sha256(relFile); // id stabile
// RECORD allineato allapp
results.push({
id, // sha256 del path relativo
name: dirent.name,
path: relFile,
thub1: relThumbMin,
thub2: relThumbAvg,
gps, // {lat,lng,alt} oppure null
data: timeRaw, // EXIF originale (legacy)
taken_at: takenAtIso, // ISO-8601 UTC
mime_type,
width,
height,
size_bytes,
location: null,
});
}
return results;
}
// -----------------------------------------------------------------------------
// MAIN
// -----------------------------------------------------------------------------
async function scanPhoto(dir) {
try {
console.log('Inizio scansione:', dir);
// dir può essere "public/photos/original" o simile
const absDir = path.isAbsolute(dir) ? dir : path.join(process.cwd(), dir);
const photos = await scanDir(absDir);
console.log('Trovate', photos.length, 'foto');
// 1) (opzionale) invio a backend /photos
if (SEND_PHOTOS && BASE_URL) {
for (const p of photos) {
try {
await postWithAuth(`${BASE_URL}/photos`, p);
} catch (err) {
console.error('Errore invio foto:', err.message);
}
}
}
// 2) (opzionale) scrivo indice statico public/photos/index.json
if (WRITE_INDEX) {
const absIndexPath = path.join(WEB_ROOT, INDEX_PATH); // es. public/photos/index.json
await fsp.mkdir(path.dirname(absIndexPath), { recursive: true });
await fsp.writeFile(absIndexPath, JSON.stringify(photos, null, 2), 'utf8');
console.log('Scritto indice:', absIndexPath);
}
console.log('Scansione completata');
return photos;
} catch (e) {
console.error('Errore generale scanPhoto:', e);
throw e;
}
}
module.exports = scanPhoto;
// Esempio di esecuzione diretta:
// node -e "require('./api_v1/scanphoto')('public/photos/original')"

400
api_v1/scanphoto.js.old Normal file
View file

@ -0,0 +1,400 @@
/* eslint-disable no-console */
require('dotenv').config();
const fs = require('fs');
const fsp = require('fs/promises');
const path = require('path');
const crypto = require('crypto');
const ExifReader = require('exifreader');
const sharp = require('sharp');
const axios = require('axios');
const { exec } = require('child_process');
const BASE_URL = process.env.BASE_URL;
const EMAIL = process.env.EMAIL;
const PASSWORD = process.env.PASSWORD;
const SEND_PHOTOS = (process.env.SEND_PHOTOS || 'true').toLowerCase() === 'true';
const WRITE_INDEX = (process.env.WRITE_INDEX || 'true').toLowerCase() === 'true';
const WEB_ROOT = process.env.WEB_ROOT || 'public';
const INDEX_PATH = process.env.INDEX_PATH || path.posix.join('photos', 'index.json');
// Estensioni supportate (immagini + video)
const SUPPORTED_EXTS = new Set([
'.jpg', '.jpeg', '.png', '.webp', '.heic', '.heif',
'.mp4', '.mov', '.m4v'
]);
// -----------------------------------------------------------------------------
// UTILS
// -----------------------------------------------------------------------------
function toPosix(p) {
return p.split(path.sep).join('/');
}
function sha256(s) {
return crypto.createHash('sha256').update(s).digest('hex');
}
function inferMimeFromExt(ext) {
switch (ext.toLowerCase()) {
case '.jpg':
case '.jpeg': return 'image/jpeg';
case '.png': return 'image/png';
case '.webp': return 'image/webp';
case '.heic':
case '.heif': return 'image/heic';
case '.mp4': return 'video/mp4';
case '.mov': return 'video/quicktime';
case '.m4v': return 'video/x-m4v';
default: return 'application/octet-stream';
}
}
function parseExifDateUtc(s) {
if (!s) return null;
const re = /^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
const m = re.exec(s);
if (!m) return null;
const dt = new Date(Date.UTC(+m[1], +m[2]-1, +m[3], +m[4], +m[5], +m[6]));
return dt.toISOString();
}
// -----------------------------------------------------------------------------
// GPS — FOTO (ExifReader)
// -----------------------------------------------------------------------------
function extractGpsFromExif(tags) {
if (!tags?.gps) return null;
const lat = tags.gps.Latitude;
const lng = tags.gps.Longitude;
const alt = tags.gps.Altitude;
if (lat == null || lng == null) return null;
return {
lat: Number(lat),
lng: Number(lng),
alt: alt != null ? Number(alt) : null
};
}
// -----------------------------------------------------------------------------
// GPS — VIDEO (exiftool) — FUNZIONA SEMPRE
// -----------------------------------------------------------------------------
function extractGpsWithExiftool(videoPath) {
return new Promise((resolve) => {
const cmd = `exiftool -n -G1 -a -gps:all -quicktime:all -user:all "${videoPath}"`;
exec(cmd, (err, stdout, stderr) => {
console.log("=== EXIFTOOL RAW OUTPUT ===");
console.log(stdout);
console.log("=== FINE RAW OUTPUT ===");
if (err || !stdout) return resolve(null);
// 1) UserData: GPS Coordinates : 44.1816 12.1251
const userData = stdout.match(/GPS Coordinates\s*:\s*([0-9\.\-]+)\s+([0-9\.\-]+)/i);
if (userData) {
return resolve({
lat: Number(userData[1]),
lng: Number(userData[2]),
alt: null
});
}
// 2) GPSLatitude / GPSLongitude (fallback)
const lat1 = stdout.match(/GPSLatitude\s*:\s*([0-9\.\-]+)/i);
const lng1 = stdout.match(/GPSLongitude\s*:\s*([0-9\.\-]+)/i);
if (lat1 && lng1) {
return resolve({
lat: Number(lat1[1]),
lng: Number(lng1[1]),
alt: null
});
}
// 3) QuickTime:GPSCoordinates (fallback)
const coords = stdout.match(/GPSCoordinates\s*:\s*([0-9\.\-]+)\s+([0-9\.\-]+)/i);
if (coords) {
return resolve({
lat: Number(coords[1]),
lng: Number(coords[2]),
alt: null
});
}
resolve(null);
});
});
}
// -----------------------------------------------------------------------------
// AUTH / POST
// -----------------------------------------------------------------------------
let cachedToken = null;
async function getToken(force = false) {
if (!SEND_PHOTOS) return null;
if (cachedToken && !force) return cachedToken;
try {
const res = await axios.post(`${BASE_URL}/auth/login`, {
email: EMAIL,
password: PASSWORD
});
cachedToken = res.data.token;
return cachedToken;
} catch (err) {
console.error('ERRORE LOGIN:', err.message);
return null;
}
}
async function postWithAuth(url, payload) {
if (!SEND_PHOTOS) return;
let token = await getToken();
if (!token) throw new Error('Token assente');
try {
await axios.post(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
timeout: 20000,
});
} catch (err) {
if (err.response && err.response.status === 401) {
token = await getToken(true);
if (!token) throw err;
await axios.post(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
timeout: 20000,
});
} else {
throw err;
}
}
}
// -----------------------------------------------------------------------------
// VIDEO: ffmpeg thumbnail
// -----------------------------------------------------------------------------
function createVideoThumbnail(videoPath, thumbMinPath, thumbAvgPath) {
return new Promise((resolve) => {
const cmd = `
ffmpeg -y -i "${videoPath}" -ss 00:00:01.000 -vframes 1 "${thumbAvgPath}" &&
ffmpeg -y -i "${thumbAvgPath}" -vf "scale=100:-1" "${thumbMinPath}"
`;
exec(cmd, (err) => {
if (err) console.error("Errore thumbnail video:", err.message);
resolve();
});
});
}
// -----------------------------------------------------------------------------
// VIDEO: ffprobe metadata
// -----------------------------------------------------------------------------
function probeVideo(videoPath) {
return new Promise((resolve) => {
const cmd = `ffprobe -v quiet -print_format json -show_format -show_streams "${videoPath}"`;
exec(cmd, (err, stdout) => {
if (err) {
console.error("Errore ffprobe:", err.message);
return resolve({});
}
try {
resolve(JSON.parse(stdout));
} catch {
resolve({});
}
});
});
}
// -----------------------------------------------------------------------------
// THUMBNAILS IMMAGINI
// -----------------------------------------------------------------------------
async function createThumbnails(filePath, thumbMinPath, thumbAvgPath) {
try {
await sharp(filePath)
.resize({ width: 100, height: 100, fit: 'inside', withoutEnlargement: true })
.withMetadata()
.toFile(thumbMinPath);
await sharp(filePath)
.resize({ width: 400, withoutEnlargement: true })
.withMetadata()
.toFile(thumbAvgPath);
} catch (err) {
console.error('Errore creazione thumbnails:', err.message, filePath);
}
}
// -----------------------------------------------------------------------------
// SCANSIONE RICORSIVA
// -----------------------------------------------------------------------------
async function scanDir(dirAbs, results = []) {
const dirEntries = await fsp.readdir(dirAbs, { withFileTypes: true });
for (const dirent of dirEntries) {
const absPath = path.join(dirAbs, dirent.name);
if (dirent.isDirectory()) {
await scanDir(absPath, results);
continue;
}
const ext = path.extname(dirent.name).toLowerCase();
if (!SUPPORTED_EXTS.has(ext)) continue;
const isVideo = ['.mp4', '.mov', '.m4v'].includes(ext);
console.log('Trovato:', absPath);
const relFile = toPosix(path.relative(WEB_ROOT, absPath));
const relDir = toPosix(path.posix.dirname(relFile));
const relThumbDir = relDir.replace(/original/i, 'thumbs');
const absThumbDir = path.join(WEB_ROOT, relThumbDir);
await fsp.mkdir(absThumbDir, { recursive: true });
const baseName = path.parse(dirent.name).name;
const absThumbMin = path.join(absThumbDir, `${baseName}_min.jpg`);
const absThumbAvg = path.join(absThumbDir, `${baseName}_avg.jpg`);
if (isVideo) {
console.log(">>> È UN VIDEO, ESTRAGGO GPS CON EXIFTOOL:", absPath);
await createVideoThumbnail(absPath, absThumbMin, absThumbAvg);
} else {
await createThumbnails(absPath, absThumbMin, absThumbAvg);
}
const relThumbMin = toPosix(path.relative(WEB_ROOT, absThumbMin));
const relThumbAvg = toPosix(path.relative(WEB_ROOT, absThumbAvg));
let tags = {};
try {
tags = await ExifReader.load(absPath, { expanded: true });
} catch {}
const timeRaw = tags?.exif?.DateTimeOriginal?.value?.[0] || null;
const takenAtIso = parseExifDateUtc(timeRaw);
let gps = null;
if (isVideo) {
gps = await extractGpsWithExiftool(absPath);
} else {
gps = extractGpsFromExif(tags);
}
let width = null, height = null, size_bytes = null, duration = null;
const st = await fsp.stat(absPath);
size_bytes = st.size;
if (isVideo) {
const info = await probeVideo(absPath);
const stream = info.streams?.find(s => s.width && s.height);
if (stream) {
width = stream.width;
height = stream.height;
}
duration = info.format?.duration || null;
} else {
try {
const meta = await sharp(absPath).metadata();
width = meta.width || null;
height = meta.height || null;
} catch {}
}
const mime_type = inferMimeFromExt(ext);
const id = sha256(relFile);
results.push({
id,
name: dirent.name,
path: relFile,
thub1: relThumbMin,
thub2: relThumbAvg,
gps,
data: timeRaw,
taken_at: takenAtIso,
mime_type,
width,
height,
size_bytes,
duration: isVideo ? duration : null,
location: null,
});
}
return results;
}
// -----------------------------------------------------------------------------
// MAIN
// -----------------------------------------------------------------------------
async function scanPhoto(dir) {
try {
console.log('Inizio scansione:', dir);
const absDir = path.isAbsolute(dir) ? dir : path.join(process.cwd(), dir);
const photos = await scanDir(absDir);
console.log('Trovati', photos.length, 'file (foto + video)');
if (SEND_PHOTOS && BASE_URL) {
for (const p of photos) {
try {
await postWithAuth(`${BASE_URL}/photos`, p);
} catch (err) {
console.error('Errore invio:', err.message);
}
}
}
if (WRITE_INDEX) {
const absIndexPath = path.join(WEB_ROOT, INDEX_PATH);
await fsp.mkdir(path.dirname(absIndexPath), { recursive: true });
await fsp.writeFile(absIndexPath, JSON.stringify(photos, null, 2), 'utf8');
console.log('Scritto indice:', absIndexPath);
}
console.log('Scansione completata');
return photos;
} catch (e) {
console.error('Errore generale scanPhoto:', e);
throw e;
}
}
module.exports = scanPhoto;

144
api_v1/scanphoto.js.orig Normal file
View file

@ -0,0 +1,144 @@
require('dotenv').config();
const fs = require('fs');
const path = require('path');
const ExifReader = require('exifreader');
const sharp = require('sharp');
const axios = require('axios');
const BASE_URL = process.env.BASE_URL;
const EMAIL = process.env.EMAIL;
const PASSWORD = process.env.PASSWORD;
// -----------------------------------------------------
// LOGIN → ottiene token JWT
// -----------------------------------------------------
async function getToken() {
try {
const res = await axios.post(`${BASE_URL}/auth/login`, {
email: EMAIL,
password: PASSWORD
});
return res.data.token;
} catch (err) {
console.error("ERRORE LOGIN:", err.message);
return null;
}
}
// -----------------------------------------------------
// INVIA FOTO AL SERVER
// -----------------------------------------------------
async function sendPhoto(json) {
const token = await getToken();
if (!token) {
console.error("Token non ottenuto, impossibile inviare foto");
return;
}
try {
await axios.post(`${BASE_URL}/photos`, json, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
}
});
} catch (err) {
console.error("Errore invio foto:", err.message);
}
}
// -----------------------------------------------------
// CREA THUMBNAILS
// -----------------------------------------------------
async function createThumbnails(filePath, thumbMinPath, thumbAvgPath) {
try {
await sharp(filePath)
.resize(100, 100, { fit: "inside" })
.withMetadata()
.toFile(thumbMinPath);
await sharp(filePath)
.resize(400)
.withMetadata()
.toFile(thumbAvgPath);
} catch (err) {
console.error("Errore creazione thumbnails:", err.message);
}
}
// -----------------------------------------------------
// SCANSIONE RICORSIVA
// -----------------------------------------------------
async function scanDir(dir, ext, results = []) {
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
await scanDir(filePath, ext, results);
continue;
}
if (path.extname(file).toLowerCase() !== ext) continue;
console.log("Trovato:", file);
const relDir = dir.replace("public/", "");
const thumbDir = path.join("public", relDir.replace("original", "thumbs"));
if (!fs.existsSync(thumbDir)) {
fs.mkdirSync(thumbDir, { recursive: true });
}
const baseName = path.parse(file).name;
const extName = path.parse(file).ext;
const thumbMin = path.join(thumbDir, `${baseName}_min${extName}`);
const thumbAvg = path.join(thumbDir, `${baseName}_avg${extName}`);
await createThumbnails(filePath, thumbMin, thumbAvg);
// EXIF
let tags = {};
try {
tags = await ExifReader.load(filePath, { expanded: true });
} catch {}
const time = tags?.exif?.DateTimeOriginal?.value?.[0] || null;
const gps = tags?.gps || null;
results.push({
name: file,
path: relDir + "/" + file,
thub1: thumbMin.replace("public/", ""),
thub2: thumbAvg.replace("public/", ""),
gps,
data: time,
location: null
});
}
return results;
}
// -----------------------------------------------------
// FUNZIONE PRINCIPALE
// -----------------------------------------------------
async function scanPhoto(dir) {
console.log("Inizio scansione:", dir);
const photos = await scanDir(dir, ".jpg");
console.log("Trovate", photos.length, "foto");
for (const p of photos) {
await sendPhoto(p);
}
console.log("Scansione completata");
}
module.exports = scanPhoto;

173
api_v1/server ok.js Normal file
View file

@ -0,0 +1,173 @@
/**
* Require necessary libraries
*/
const fs = require('fs')
const bodyParser = require('body-parser')
const jsonServer = require('json-server')
const jwt = require('jsonwebtoken')
const bcrypt = require('bcrypt')
//const open = require('open');
const path = require('path');
const scanPhoto = require('./scanphoto.js')
// JWT confing data
const SECRET_KEY = '123456789'
const expiresIn = '1h'
// Create server
var server = jsonServer.create()
// Create router
if(fs.existsSync('./api_v1/db.json')){
var router = jsonServer.router('./api_v1/db.json')
} else {
const initialData = fs.readFileSync('api_v1/initialDB.json', 'utf8');
// to update (sync) current database (db.json) file
fs.writeFileSync('api_v1/db.json', initialData);
var router = jsonServer.router('./api_v1/db.json')
}
// Create router
var router = jsonServer.router('./api_v1/db.json')
// Users database
const userdb = JSON.parse(fs.readFileSync('./api_v1/users.json', 'UTF-8'))
// Default middlewares
server.use(bodyParser.urlencoded({ extended: true }))
server.use(bodyParser.json())
// Create a token from a payload
function createToken(payload) {
return jwt.sign(payload, SECRET_KEY, { expiresIn })
}
// Verify the token
function verifyToken(token) {
return jwt.verify(
token,
SECRET_KEY,
(err, decode) => (decode !== undefined ? decode : err)
)
}
// Check if the user exists in database
function isAuthenticated({ email, password }) {
return (
userdb.users.findIndex(
user =>
user.email === email && bcrypt.compareSync(password, user.password)
) !== -1
)
}
function azz(){
const initialData = fs.readFileSync('api_v1/initialDB.json', 'utf8');
// to update (sync) current database (db.json) file
fs.writeFileSync('api_v1/db.json', initialData);
router.db.setState(JSON.parse(initialData));
console.log('DB resettato');
}
// con 192.168.1.3:7771/ apre http:192.168.1.3:7771/public.index.html
server.get('/', (req, res) => {
//console.log(req.query)
res.sendFile(path.resolve("public/index.html"))
})
// scansiona le foto
server.get('/scan', async (req, res) => {
azz();
await scanPhoto('./public/photos/original')
console.log("Ricaricato")
res.send({status: 'Ricaricato'})
})
// esempio http:192.168.1.3:7771/files?file=mio.txt
server.get('/files', (req, res) => {
console.log(req.query)
res.sendFile(path.resolve("public/"+req.query.file))
})
server.get('/initDB1',(req, res, next) => {
const Data = { photos: []};
// to update (sync) current database (db.json) file
fs.writeFileSync('api_v1/db.json', JSON.stringify(Data));
router.db.setState(Data);
res.send({status: 'DB resettato'});
//res.sendStatus(200);
});
server.get('/initDB',(req, res, next) => {
const initialData = fs.readFileSync('api_v1/initialDB.json', 'utf8');
// to update (sync) current database (db.json) file
fs.writeFileSync('api_v1/db.json', initialData);
router.db.setState(JSON.parse(initialData));
//router = jsonServer.router('./api_v1/db.json')
res.send({status: 'DB resettato'});
//res.sendStatus(200);
});
server.get('/log', (req, res) => {
console.log(server)
})
server.use((req, res, next) => {
//console.log(req.headers);
//console.log(req.method);
var a = req.path.split("/");
if (req.method === 'GET' && a[1] == 'pub' && a.length > 2) {
//console.log(req.headers.host);
//console.log(a.slice(2).join("/"));
res.status(200).sendFile(path.resolve("public/"+a.slice(2).join("/")));
//res.sendStatus(200);
} else {
next();
}
})
/**
* Method: POST
* Endpoint: /auth/login
*/
server.post('/auth/login', (req, res) => {
const { email, password } = req.body
if (isAuthenticated({ email, password }) === false) {
const status = 401
const message = 'Incorrect email or password'
res.status(status).json({ status, message })
return
}
const token = createToken({ email, password })
res.status(200).json({ token })
})
/**
* Middleware: Check authorization
*/
server.use(/^(?!\/auth).*$/, (req, res, next) => {
if (
req.headers.authorization === undefined ||
req.headers.authorization.split(' ')[0] !== 'Bearer'
) {
const status = 401
const message = 'Bad authorization header'
res.status(status).json({ status, message })
return
}
try {
verifyToken(req.headers.authorization.split(' ')[1])
next()
} catch (err) {
const status = 401
const message = 'Error: access_token is not valid'
res.status(status).json({ status, message })
}
})
// Server mount
server.use(router)
server.listen(3000, () => {
console.log('Auth API server runing on port 3000 ...')
})

152
api_v1/server.js Normal file
View file

@ -0,0 +1,152 @@
require('dotenv').config();
const fs = require('fs');
const bodyParser = require('body-parser');
const jsonServer = require('json-server');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const path = require('path');
const scanPhoto = require('./scanphoto.js');
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 = jsonServer.create();
// -----------------------------------------------------
// STATIC FILES
// -----------------------------------------------------
server.use(jsonServer.defaults({
static: path.join(__dirname, '../public')
}));
// -----------------------------------------------------
// CONFIG ENDPOINT (PUBBLICO)
// -----------------------------------------------------
server.get('/config', (req, res) => {
res.json({
baseUrl: process.env.BASE_URL
});
});
// -----------------------------------------------------
// ROUTER DB
// -----------------------------------------------------
let router;
if (fs.existsSync('./api_v1/db.json')) {
router = jsonServer.router('./api_v1/db.json');
} else {
const initialData = fs.readFileSync('api_v1/initialDB.json', 'utf8');
fs.writeFileSync('api_v1/db.json', initialData);
router = jsonServer.router('./api_v1/db.json');
}
// -----------------------------------------------------
// USERS DB
// -----------------------------------------------------
const userdb = JSON.parse(fs.readFileSync('./api_v1/users.json', 'UTF-8'));
server.use(bodyParser.urlencoded({ extended: true }));
server.use(bodyParser.json());
// -----------------------------------------------------
// JWT HELPERS
// -----------------------------------------------------
function createToken(payload) {
return jwt.sign(payload, SECRET_KEY, { expiresIn: EXPIRES_IN });
}
function verifyToken(token) {
return jwt.verify(token, SECRET_KEY, (err, decode) => decode || err);
}
function isAuthenticated({ email, password }) {
return userdb.users.findIndex(
user => user.email === email && bcrypt.compareSync(password, user.password)
) !== -1;
}
// -----------------------------------------------------
// RESET DB
// -----------------------------------------------------
function resetDB() {
const initialData = fs.readFileSync('api_v1/initialDB.json', 'utf8');
fs.writeFileSync('api_v1/db.json', initialData);
router.db.setState(JSON.parse(initialData));
console.log('DB resettato');
}
// -----------------------------------------------------
// HOME
// -----------------------------------------------------
server.get('/', (req, res) => {
res.sendFile(path.resolve("public/index.html"));
});
// -----------------------------------------------------
// SCAN FOTO
// -----------------------------------------------------
server.get('/scan', async (req, res) => {
resetDB();
await scanPhoto('./public/photos/original');
console.log("Ricaricato");
res.send({ status: 'Ricaricato' });
});
// -----------------------------------------------------
// FILE STATICI
// -----------------------------------------------------
server.get('/files', (req, res) => {
res.sendFile(path.resolve("public/" + req.query.file));
});
// -----------------------------------------------------
// RESET DB MANUALE
// -----------------------------------------------------
server.get('/initDB', (req, res) => {
resetDB();
res.send({ status: 'DB resettato' });
});
// -----------------------------------------------------
// LOGIN (PUBBLICO)
// -----------------------------------------------------
server.post('/auth/login', (req, res) => {
const { email, password } = req.body;
if (!isAuthenticated({ email, password })) {
return res.status(401).json({ status: 401, message: 'Incorrect email or password' });
}
const token = createToken({ email });
res.status(200).json({ token });
});
// -----------------------------------------------------
// JWT MIDDLEWARE (TUTTO IL RESTO È PROTETTO)
// -----------------------------------------------------
server.use(/^(?!\/auth).*$/, (req, res, next) => {
if (!req.headers.authorization || req.headers.authorization.split(' ')[0] !== 'Bearer') {
return res.status(401).json({ status: 401, message: 'Bad authorization header' });
}
try {
verifyToken(req.headers.authorization.split(' ')[1]);
next();
} catch (err) {
res.status(401).json({ status: 401, message: 'Error: access_token is not valid' });
}
});
// -----------------------------------------------------
// ROUTER JSON-SERVER
// -----------------------------------------------------
server.use(router);
// -----------------------------------------------------
// START SERVER
// -----------------------------------------------------
server.listen(PORT, () => {
console.log(`Auth API server running on port ${PORT} ...`);
});

152
api_v1/server.js.old Normal file
View file

@ -0,0 +1,152 @@
require('dotenv').config();
const fs = require('fs');
const bodyParser = require('body-parser');
const jsonServer = require('json-server');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const path = require('path');
const scanPhoto = require('./scanphoto.js');
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 = jsonServer.create();
// -----------------------------------------------------
// STATIC FILES
// -----------------------------------------------------
server.use(jsonServer.defaults({
static: path.join(__dirname, '../public')
}));
// -----------------------------------------------------
// CONFIG ENDPOINT (PUBBLICO)
// -----------------------------------------------------
server.get('/config', (req, res) => {
res.json({
baseUrl: process.env.BASE_URL
});
});
// -----------------------------------------------------
// ROUTER DB
// -----------------------------------------------------
let router;
if (fs.existsSync('./api_v1/db.json')) {
router = jsonServer.router('./api_v1/db.json');
} else {
const initialData = fs.readFileSync('api_v1/initialDB.json', 'utf8');
fs.writeFileSync('api_v1/db.json', initialData);
router = jsonServer.router('./api_v1/db.json');
}
// -----------------------------------------------------
// USERS DB
// -----------------------------------------------------
const userdb = JSON.parse(fs.readFileSync('./api_v1/users.json', 'UTF-8'));
server.use(bodyParser.urlencoded({ extended: true }));
server.use(bodyParser.json());
// -----------------------------------------------------
// JWT HELPERS
// -----------------------------------------------------
function createToken(payload) {
return jwt.sign(payload, SECRET_KEY, { expiresIn: EXPIRES_IN });
}
function verifyToken(token) {
return jwt.verify(token, SECRET_KEY, (err, decode) => decode || err);
}
function isAuthenticated({ email, password }) {
return userdb.users.findIndex(
user => user.email === email && bcrypt.compareSync(password, user.password)
) !== -1;
}
// -----------------------------------------------------
// RESET DB
// -----------------------------------------------------
function resetDB() {
const initialData = fs.readFileSync('api_v1/initialDB.json', 'utf8');
fs.writeFileSync('api_v1/db.json', initialData);
router.db.setState(JSON.parse(initialData));
console.log('DB resettato');
}
// -----------------------------------------------------
// HOME
// -----------------------------------------------------
server.get('/', (req, res) => {
res.sendFile(path.resolve("public/index.html"));
});
// -----------------------------------------------------
// SCAN FOTO
// -----------------------------------------------------
server.get('/scan', async (req, res) => {
resetDB();
await scanPhoto('./public/photos/original');
console.log("Ricaricato");
res.send({ status: 'Ricaricato' });
});
// -----------------------------------------------------
// FILE STATICI
// -----------------------------------------------------
server.get('/files', (req, res) => {
res.sendFile(path.resolve("public/" + req.query.file));
});
// -----------------------------------------------------
// RESET DB MANUALE
// -----------------------------------------------------
server.get('/initDB', (req, res) => {
resetDB();
res.send({ status: 'DB resettato' });
});
// -----------------------------------------------------
// LOGIN (PUBBLICO)
// -----------------------------------------------------
server.post('/auth/login', (req, res) => {
const { email, password } = req.body;
if (!isAuthenticated({ email, password })) {
return res.status(401).json({ status: 401, message: 'Incorrect email or password' });
}
const token = createToken({ email });
res.status(200).json({ token });
});
// -----------------------------------------------------
// JWT MIDDLEWARE (TUTTO IL RESTO È PROTETTO)
// -----------------------------------------------------
server.use(/^(?!\/auth).*$/, (req, res, next) => {
if (!req.headers.authorization || req.headers.authorization.split(' ')[0] !== 'Bearer') {
return res.status(401).json({ status: 401, message: 'Bad authorization header' });
}
try {
verifyToken(req.headers.authorization.split(' ')[1]);
next();
} catch (err) {
res.status(401).json({ status: 401, message: 'Error: access_token is not valid' });
}
});
// -----------------------------------------------------
// ROUTER JSON-SERVER
// -----------------------------------------------------
server.use(router);
// -----------------------------------------------------
// START SERVER
// -----------------------------------------------------
server.listen(PORT, () => {
console.log(`Auth API server running on port ${PORT} ...`);
});

11
api_v1/super install.txt Normal file
View file

@ -0,0 +1,11 @@
sudo docker run -it -d -p 7771:3000 -v /home/nvme/plexmediafiles/server/svr:/jwt-json-server/api_v1 -v /home/nvme/plexmediafiles/server/public:/jwt-json-server/public --name json-server-auth node:latest
sudo docker exec -it json-server-auth /bin/bash
apt update
apt upgrade -y
apt install nano
git clone https://git.patachina.duckdns.org/Fabio/json-server-auth.git jwt-json-server1
cp -R jwt-json-server1/* jwt-json-server
npm i exifreader
npm i path
npm i sharp
npm i axios

51
api_v1/tools.js Normal file
View file

@ -0,0 +1,51 @@
/**
* Required libraries
*/
const bcrypt = require('bcrypt')
const readLine = require('readline')
const async = require('async')
// Password hash method
const hashPassword = plain => bcrypt.hashSync(plain, 8)
// Ask user password method
function askPassword(question) {
return new Promise((resolve, reject) => {
const rl = readLine.createInterface({
input: process.stdin,
output: process.stdout
})
rl.question(question, answer => {
rl.close()
resolve(answer)
})
})
}
// Generate hash password method
async function generateHash() {
try {
console.log('**********************************')
console.log('** Password hash script **')
console.log('**********************************')
const passwordAnswer = await askPassword(
'Please give me a password to hash: '
)
if (passwordAnswer != '') {
const hashedPassword = hashPassword(passwordAnswer)
const compare = bcrypt.compareSync(passwordAnswer, hashedPassword)
await console.log('Hashed password:', hashedPassword)
await console.log('Valdiation:', compare)
} else {
console.log('You need write something. Script aborted!')
}
} catch (err) {
console.log(err)
return process.exit(1)
}
}
generateHash()

28
api_v1/users.json Normal file
View file

@ -0,0 +1,28 @@
{
"users": [
{
"id": 1,
"name": "Freddie",
"email": "freddie@queen.com",
"password": "$2b$08$BjbCY62KrjAvvqs/cURqFu5F4vgcsAwgHDxSAn/kX5lHjLFPQLrn."
},
{
"id": 2,
"name": "Brian",
"email": "brian@queen.com",
"password": "$2b$08$BjbCY62KrjAvvqs/cURqFu5F4vgcsAwgHDxSAn/kX5lHjLFPQLrn."
},
{
"id": 3,
"name": "Rogery",
"email": "roger@queen.com",
"password": "$2b$08$BjbCY62KrjAvvqs/cURqFu5F4vgcsAwgHDxSAn/kX5lHjLFPQLrn."
},
{
"id": 4,
"name": "Fabio",
"email": "fabio@gmail.com",
"password": "$2b$08$g0UWN6RnN7e.8rX3fuXSSOSJDTvucu./0FAU.yXp0wx4SJXyeaU3."
}
]
}

0
db.json Normal file
View file

143
index.html Normal file
View file

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<h1>This is a Heading</h1>
<p>This is a paragraph.</p>
<button type="button" onclick="scan()">Scan</button>
<button type="button" onclick="read()">Read</button>
<button type="button" onclick="writ()">write</button>
<button type="button" onclick="azz()">Azz</button>
<button type="button" onclick="log()">Log DB</button>
<button type="button" onclick="myReset()">Reset</button>
<script>
var tok;
var db;
function read() {
myGet();
}
function writ() {
myPost({'email':'fabio@gmail.com', 'password':'master66'})
}
function scan() {
fetch('http://192.168.1.3:7771/scan', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
}
function log() {
lineGet("log")
console.log("log inviato");
}
function myReset() {
lineGet("initDB")
console.log("reset inviato");
}
async function azz(){
await azz1();
console.log("azzerato");
}
async function azz1() {
for(let v =0; v < db.length; v++){
//console.log(db[v].id);
myDel(db[v].id);
if (v==db.length-1){
return;
}
}
}
function toc() {
fetch('http://192.168.1.3:7771/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({'email':'fabio@gmail.com', 'password':'master66'}),
})
.then(response => response.json())
.then(user1 => {
tok = user1.token;
console.log(tok);
})
}
function myPost(json) {
const myHeaders = new Headers();
myHeaders.append('Authorization', 'Bearer ' + tok);
myHeaders.append('Content-Type', 'application/json');
//console.log(myHeaders.get("Content-Type"));
//console.log(myHeaders.get("Authorization"));
fetch('http://192.168.1.3:7771/photos', {
method: 'POST',
headers: myHeaders,
body: JSON.stringify(json),
})
.then(response => response.json())
.then(user => console.log(user));
}
function myGet() {
const myHeaders = new Headers();
myHeaders.append('Authorization', 'Bearer ' + tok);
fetch('http://192.168.1.3:7771/photos', {
method: 'GET',
headers: myHeaders,
})
.then(response => response.json())
.then(user => {
console.log(user);
db=user;
});
}
function myDel(id) {
const myHeaders = new Headers();
myHeaders.append('Authorization', 'Bearer ' + tok);
myHeaders.append('Content-Type', 'application/json');
//console.log(myHeaders.get("Content-Type"));
//console.log(myHeaders.get("Authorization"));
fetch('http://192.168.1.3:7771/photos/'+id, {
method: 'DELETE',
headers: myHeaders,
})
.then(response => response.json())
.then(user => console.log(""));
}
function lineGet(dir) {
const myHeaders = new Headers();
myHeaders.append('Authorization', 'Bearer ' + tok);
fetch('http://192.168.1.3:7771/'+dir, {
method: 'GET',
headers: myHeaders,
})
.then(response => response.json())
.then(user => {
console.log(user);
});
}
toc();
myGet();
</script>
</body>
</html>

21
license.md Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 - Igor Antun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2714
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

39
package.json Normal file
View file

@ -0,0 +1,39 @@
{
"name": "jwt-json-server",
"version": "0.0.1",
"description": "Building a Fake and JWT Protected REST API with json-server",
"main": "index.js",
"scripts": {
"start-no-auth": "json-server --watch ./api_v1/db.json --host 0.0.0.0 -p 4000 -s ./public",
"start": "node ./api_v1/server.js --host 0.0.0.0 -p 4000 -s ./public",
"mock-data": "node ./api_v1/generateData.js > ./api_v1/db.json",
"hash": "node ./api_v1/tools.js",
"search": "node ./api_v1/search.js",
"photo": "node ./api_v1/photo.js",
"photo1": "node ./api_v1/photo1.js",
"geo": "node ./api_v1/georev.js",
"geo1": "node ./api_v1/georev1.js",
"geo2": "node ./api_v1/georev2.js",
"geo3": "node ./api_v1/georev3.js"
},
"keywords": [
"api"
],
"author": "Fabio",
"license": "MIT",
"dependencies": {
"@tensorflow/tfjs-core": "^4.21.0",
"async": "^3.2.6",
"axios": "^1.7.7",
"bcrypt": "^5.1.1",
"dotenv": "^17.3.1",
"exifreader": "^4.23.6",
"face-api.js": "^0.20.0",
"faker": "^5.5.3",
"json-server": "^0.17.2",
"jsonwebtoken": "^9.0.2",
"path": "^0.12.7",
"ramda": "^0.30.1",
"sharp": "^0.33.5"
}
}

88
public/index.html Normal file
View file

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Photo Manager</title>
</head>
<body>
<div id="login-box" style="padding:20px;">
<h2>Login</h2>
<input id="email" type="text" placeholder="Email"><br><br>
<input id="password" type="password" placeholder="Password"><br><br>
<button onclick="login()">Accedi</button>
</div>
<div id="app" style="display:none;">
<h2>Gestione Foto</h2>
<button onclick="scan()">Scansiona Foto</button>
<button onclick="resetDB()">Reset DB</button>
<pre id="out"></pre>
</div>
<script>
let BASE_URL = null;
let token = null;
async function loadConfig() {
const res = await fetch('/config');
const cfg = await res.json();
BASE_URL = cfg.baseUrl;
}
async function login() {
const email = document.getElementById("email").value;
const password = document.getElementById("password").value;
const res = await fetch(`${BASE_URL}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password })
});
if (!res.ok) {
alert("Credenziali errate");
return;
}
const data = await res.json();
token = data.token;
document.getElementById("login-box").style.display = "none";
document.getElementById("app").style.display = "block";
await readDB();
}
async function readDB() {
const res = await fetch(`${BASE_URL}/photos`, {
headers: { "Authorization": "Bearer " + token }
});
const db = await res.json();
document.getElementById("out").textContent = JSON.stringify(db, null, 2);
}
async function scan() {
await fetch(`${BASE_URL}/scan`, {
headers: { "Authorization": "Bearer " + token }
});
await readDB();
}
async function resetDB() {
await fetch(`${BASE_URL}/initDB`, {
headers: { "Authorization": "Bearer " + token }
});
await readDB();
}
window.onload = async () => {
await loadConfig();
};
</script>
</body>
</html>

82
public/index.html.old Normal file
View file

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Gestione Foto</title>
</head>
<body>
<h1>Gestione Foto</h1>
<button onclick="scan()">Scansiona</button>
<button onclick="readDB()">Leggi DB</button>
<button onclick="resetDB()">Reset DB</button>
<button onclick="deleteAll()">Cancella tutto</button>
<pre id="out"></pre>
<script>
let BASE_URL = null;
let tok = null;
let db = [];
async function loadConfig() {
const res = await fetch('/config');
const cfg = await res.json();
BASE_URL = cfg.baseUrl;
}
async function start() {
await loadConfig();
await login();
await readDB();
}
async function login() {
const res = await fetch(`${BASE_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'fabio@gmail.com', password: 'master66' })
});
const data = await res.json();
tok = data.token;
}
async function readDB() {
const res = await fetch(`${BASE_URL}/photos`, {
headers: { 'Authorization': 'Bearer ' + tok }
});
db = await res.json();
document.getElementById('out').textContent = JSON.stringify(db, null, 2);
}
async function scan() {
await fetch(`${BASE_URL}/scan`, {
headers: { 'Authorization': 'Bearer ' + tok }
});
await readDB();
}
async function resetDB() {
await fetch(`${BASE_URL}/initDB`, {
headers: { 'Authorization': 'Bearer ' + tok }
});
await readDB();
}
async function deleteAll() {
for (const item of db) {
await fetch(`${BASE_URL}/photos/${item.id}`, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + tok }
});
}
await readDB();
}
start();
</script>
</body>
</html>

2905
public/photos/index.json Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Some files were not shown because too many files have changed in this diff Show more