Move to multi-files
This commit is contained in:
parent
3c70c1d53d
commit
78ca8f815a
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
50
index.js
50
index.js
@ -3,6 +3,8 @@ const express = require('express');
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const sqlite3 = require('sqlite3').verbose();
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
@ -13,6 +15,28 @@ const certificate = fs.readFileSync(path.join(__dirname, 'server.cert'), 'utf8')
|
|||||||
|
|
||||||
const credentials = { key: privateKey, cert: certificate };
|
const credentials = { key: privateKey, cert: certificate };
|
||||||
|
|
||||||
|
// Middleware to parse JSON bodies
|
||||||
|
app.use(express.json()); // Use built-in body-parser for JSON
|
||||||
|
|
||||||
|
// Set up the SQLite database
|
||||||
|
const db = new sqlite3.Database('./books.db', (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Could not connect to database', err);
|
||||||
|
} else {
|
||||||
|
console.log('Connected to database');
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS books (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
isbn TEXT,
|
||||||
|
title TEXT,
|
||||||
|
authors TEXT,
|
||||||
|
publishedDate TEXT,
|
||||||
|
description TEXT
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Serve static files from the 'public' directory
|
// Serve static files from the 'public' directory
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
@ -27,6 +51,8 @@ app.get('/book/:isbn', async (req, res) => {
|
|||||||
const bookData = openLibraryResponse.data[`ISBN:${isbn}`];
|
const bookData = openLibraryResponse.data[`ISBN:${isbn}`];
|
||||||
|
|
||||||
if (bookData) {
|
if (bookData) {
|
||||||
|
console.log('Book data found in Open Library');
|
||||||
|
console.log(bookData);
|
||||||
res.json(formatOpenLibraryData(bookData));
|
res.json(formatOpenLibraryData(bookData));
|
||||||
} else {
|
} else {
|
||||||
// Fallback to Internet Archive if no data from Open Library
|
// Fallback to Internet Archive if no data from Open Library
|
||||||
@ -45,23 +71,41 @@ app.get('/book/:isbn', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Endpoint to store book in the database
|
||||||
|
app.post('/store-book', (req, res) => {
|
||||||
|
const { isbn, title, authors, publishedDate, description } = req.body;
|
||||||
|
|
||||||
|
const query = `INSERT INTO books (isbn, title, authors, publishedDate, description) VALUES (?, ?, ?, ?, ?)`;
|
||||||
|
const params = [isbn, title, authors ? authors.join(', ') : '', publishedDate, description || ''];
|
||||||
|
|
||||||
|
db.run(query, params, function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error storing book in database:', err);
|
||||||
|
res.status(500).json({ error: 'Failed to store book in database' });
|
||||||
|
} else {
|
||||||
|
res.json({ success: true, message: 'Book stored successfully', bookId: this.lastID });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function formatOpenLibraryData(data) {
|
function formatOpenLibraryData(data) {
|
||||||
return {
|
return {
|
||||||
|
isbn: data.identifiers.isbn_13 ? data.identifiers.isbn_13[0] : '',
|
||||||
title: data.title,
|
title: data.title,
|
||||||
authors: data.authors ? data.authors.map(author => author.name) : [],
|
authors: data.authors ? data.authors.map(author => author.name) : [],
|
||||||
publishedDate: data.publish_date,
|
publishedDate: data.publish_date,
|
||||||
description: data.excerpts ? data.excerpts[0].text : 'No description available',
|
description: data.excerpts ? data.excerpts[0].text : 'No description available',
|
||||||
cover: data.cover ? data.cover.large : ''
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatArchiveData(data) {
|
function formatArchiveData(data) {
|
||||||
return {
|
return {
|
||||||
|
isbn: data.isbn ? data.isbn[0] : '',
|
||||||
title: data.title,
|
title: data.title,
|
||||||
authors: data.creator,
|
authors: data.creator,
|
||||||
publishedDate: data.date,
|
publishedDate: data.date,
|
||||||
description: data.description ? data.description[0] : 'No description available',
|
description: data.description ? data.description[0] : 'No description available',
|
||||||
cover: data.cover ? `https://archive.org/services/img/${data.identifier}` : ''
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,4 +113,4 @@ const httpsServer = https.createServer(credentials, app);
|
|||||||
|
|
||||||
httpsServer.listen(PORT, () => {
|
httpsServer.listen(PORT, () => {
|
||||||
console.log(`HTTPS Server running on https://localhost:${PORT}`);
|
console.log(`HTTPS Server running on https://localhost:${PORT}`);
|
||||||
});
|
});
|
||||||
|
|||||||
1363
node_modules/.package-lock.json
generated
vendored
1363
node_modules/.package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
1366
package-lock.json
generated
1366
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.5",
|
"axios": "^1.7.5",
|
||||||
"express": "^4.19.2"
|
"express": "^4.19.2",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,23 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>ISBN Scanner</title>
|
<title>ISBN Scanner</title>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
|
<link rel="stylesheet" href="styles.css">
|
||||||
<style>
|
|
||||||
#interactive.viewport {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 400px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
canvas.drawing, canvas.drawingBuffer {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
#book-info {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Scan a Book ISBN</h1>
|
<h1>Scan a Book ISBN</h1>
|
||||||
@ -35,153 +19,17 @@
|
|||||||
<input type="file" id="barcode-input" accept="image/*">
|
<input type="file" id="barcode-input" accept="image/*">
|
||||||
</div>
|
</div>
|
||||||
<div id="book-info"></div>
|
<div id="book-info"></div>
|
||||||
|
<div id="prompt">
|
||||||
<script>
|
<p id="prompt-message"></p>
|
||||||
let selectedDeviceId;
|
<button id="confirm">Yes</button>
|
||||||
|
<button id="edit-title">No, I'll add a title</button>
|
||||||
async function testCameraAccess() {
|
</div>
|
||||||
try {
|
<div id="title-input">
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
<label for="manual-title">Title:</label>
|
||||||
console.log('Camera access granted', stream);
|
<input type="text" id="manual-title">
|
||||||
} catch (error) {
|
<button id="save-title">Save</button>
|
||||||
console.error('Error accessing camera:', error);
|
</div>
|
||||||
}
|
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
|
||||||
}
|
<script src="script.js"></script>
|
||||||
|
|
||||||
|
|
||||||
// Get available video input devices (cameras)
|
|
||||||
async function getCameras() {
|
|
||||||
try {
|
|
||||||
// Ensure the cameraSelect element is defined before accessing it
|
|
||||||
const cameraSelect = document.getElementById('camera-select');
|
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ video: true }); // Prompt for camera access
|
|
||||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
||||||
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
|
||||||
|
|
||||||
// Update selectedDeviceId when user selects a camera
|
|
||||||
cameraSelect.addEventListener('change', function() {
|
|
||||||
selectedDeviceId = this.value;
|
|
||||||
});
|
|
||||||
if (videoDevices.length > 0) {
|
|
||||||
videoDevices.forEach((device, index) => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = device.deviceId;
|
|
||||||
option.text = device.label || `Camera ${index + 1}`;
|
|
||||||
cameraSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log("No video devices found.");
|
|
||||||
cameraSelect.innerHTML = '<option>No cameras found</option>';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error accessing the camera or enumerating devices:", error);
|
|
||||||
document.getElementById('camera-select').innerHTML = '<option>'+error+'</option>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function startScanner() {
|
|
||||||
if (!selectedDeviceId) {
|
|
||||||
alert('No camera selected or available.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Quagga.init({
|
|
||||||
inputStream: {
|
|
||||||
name: "Live",
|
|
||||||
type: "LiveStream",
|
|
||||||
target: document.querySelector('#interactive'),
|
|
||||||
constraints: {
|
|
||||||
deviceId: selectedDeviceId,
|
|
||||||
facingMode: "environment", // Default to rear camera
|
|
||||||
},
|
|
||||||
},
|
|
||||||
decoder: {
|
|
||||||
readers: ["ean_reader"] // EAN is the standard format for ISBN-13 barcodes
|
|
||||||
}
|
|
||||||
}, function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("Initialization finished. Ready to start");
|
|
||||||
Quagga.start();
|
|
||||||
});
|
|
||||||
|
|
||||||
Quagga.onDetected(async function (data) {
|
|
||||||
const isbn = data.codeResult.code;
|
|
||||||
console.log("Detected ISBN:", isbn);
|
|
||||||
Quagga.stop(); // Stop the scanner once an ISBN is detected
|
|
||||||
|
|
||||||
// Fetch book details
|
|
||||||
fetchBookInfo(isbn);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchBookInfo(isbn) {
|
|
||||||
fetch(`/book/${isbn}`)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => displayBookInfo(data))
|
|
||||||
.catch(err => console.error('Error fetching book data:', err));
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayBookInfo(data) {
|
|
||||||
const bookInfoDiv = document.getElementById('book-info');
|
|
||||||
if (data.title) {
|
|
||||||
bookInfoDiv.innerHTML = `
|
|
||||||
<h2>${data.title}</h2>
|
|
||||||
<p><strong>Authors:</strong> ${data.authors.join(', ')}</p>
|
|
||||||
<p><strong>Published Date:</strong> ${data.publishedDate}</p>
|
|
||||||
<p><strong>Description:</strong> ${data.description}</p>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
bookInfoDiv.innerHTML = '<p>No book found for this ISBN.</p>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle image file input
|
|
||||||
document.getElementById('barcode-input').addEventListener('change', function(event) {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
const imageSrc = e.target.result;
|
|
||||||
decodeBarcodeFromImage(imageSrc);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function decodeBarcodeFromImage(imageSrc) {
|
|
||||||
Quagga.decodeSingle({
|
|
||||||
src: imageSrc,
|
|
||||||
numOfWorkers: 0, // Needs to be 0 when used with decodeSingle()
|
|
||||||
inputStream: {
|
|
||||||
size: 800 // Limit the size for better performance
|
|
||||||
},
|
|
||||||
decoder: {
|
|
||||||
readers: ["ean_reader"] // EAN is the standard format for ISBN-13 barcodes
|
|
||||||
}
|
|
||||||
}, function(result) {
|
|
||||||
if (result && result.codeResult) {
|
|
||||||
console.log("Detected ISBN from image:", result.codeResult.code);
|
|
||||||
fetchBookInfo(result.codeResult.code);
|
|
||||||
} else {
|
|
||||||
console.log("Barcode not detected in image.");
|
|
||||||
document.getElementById('book-info').innerHTML = '<p>Barcode not detected in the image.</p>';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the scanner when the start button is clicked
|
|
||||||
document.getElementById('start-scanner').addEventListener('click', startScanner);
|
|
||||||
|
|
||||||
// Get cameras on page load
|
|
||||||
window.onload = getCameras;
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
114
public/script.js
Normal file
114
public/script.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
let selectedDeviceId;
|
||||||
|
let isScanning = false; // Flag to prevent multiple scans at the same time
|
||||||
|
|
||||||
|
async function testCameraAccess() {
|
||||||
|
try {
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||||||
|
console.log('Camera access granted', stream);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error accessing camera:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get available video input devices (cameras)
|
||||||
|
async function getCameras() {
|
||||||
|
try {
|
||||||
|
const cameraSelect = document.getElementById('camera-select');
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({ video: true }); // Prompt for camera access
|
||||||
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
|
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
||||||
|
|
||||||
|
// Update selectedDeviceId when user selects a camera
|
||||||
|
cameraSelect.addEventListener('change', function() {
|
||||||
|
selectedDeviceId = this.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (videoDevices.length > 0) {
|
||||||
|
videoDevices.forEach((device, index) => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = device.deviceId;
|
||||||
|
option.text = device.label || `Camera ${index + 1}`;
|
||||||
|
cameraSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("No video devices found.");
|
||||||
|
cameraSelect.innerHTML = '<option>No cameras found</option>';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error accessing the camera or enumerating devices:", error);
|
||||||
|
document.getElementById('camera-select').innerHTML = '<option>' + error + '</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startScanner() {
|
||||||
|
if (!selectedDeviceId) {
|
||||||
|
alert('No camera selected or available.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quagga.init({
|
||||||
|
inputStream: {
|
||||||
|
name: "Live",
|
||||||
|
type: "LiveStream",
|
||||||
|
target: document.querySelector('#interactive'),
|
||||||
|
constraints: {
|
||||||
|
deviceId: selectedDeviceId,
|
||||||
|
facingMode: "environment", // Default to rear camera
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decoder: {
|
||||||
|
readers: ["ean_reader"] // EAN is the standard format for ISBN-13 barcodes
|
||||||
|
}
|
||||||
|
}, function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Initialization finished. Ready to start");
|
||||||
|
Quagga.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
Quagga.onDetected(async function (data) {
|
||||||
|
if (isScanning) return; // Prevent further scans while a scan is being processed
|
||||||
|
isScanning = true; // Set the scanning flag
|
||||||
|
|
||||||
|
const isbn = data.codeResult.code;
|
||||||
|
console.log("Detected ISBN:", isbn);
|
||||||
|
Quagga.stop(); // Stop the scanner once an ISBN is detected
|
||||||
|
|
||||||
|
// Fetch book details
|
||||||
|
await fetchBookInfo(isbn);
|
||||||
|
|
||||||
|
isScanning = false; // Reset the scanning flag once processing is done
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchBookInfo(isbn) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/book/${isbn}`);
|
||||||
|
const bookData = await response.json();
|
||||||
|
|
||||||
|
if (bookData.title) {
|
||||||
|
promptUserWithBook(bookData);
|
||||||
|
} else {
|
||||||
|
console.log("No book data found. Restarting scanner...");
|
||||||
|
startScanner(); // Restart the scanner if no book information is found
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching book data:', error);
|
||||||
|
startScanner(); // Restart the scanner on error as well
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function promptUserWithBook(bookData) {
|
||||||
|
// Display book information or prompt the user for confirmation
|
||||||
|
document.getElementById('book-info').textContent = `Title: ${bookData.title}`;
|
||||||
|
document.getElementById('prompt').style.display = 'block';
|
||||||
|
// Additional logic for user confirmation can be added here
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the scanner when the start button is clicked
|
||||||
|
document.getElementById('start-scanner').addEventListener('click', startScanner);
|
||||||
|
|
||||||
|
// Get cameras on page load
|
||||||
|
window.onload = getCameras;
|
||||||
25
public/styles.css
Normal file
25
public/styles.css
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#interactive.viewport {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawing, canvas.drawingBuffer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#book-info {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#prompt {
|
||||||
|
display: none;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user