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 fs = require('fs');
|
||||
const path = require('path');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
const app = express();
|
||||
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 };
|
||||
|
||||
// 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
|
||||
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}`];
|
||||
|
||||
if (bookData) {
|
||||
console.log('Book data found in Open Library');
|
||||
console.log(bookData);
|
||||
res.json(formatOpenLibraryData(bookData));
|
||||
} else {
|
||||
// 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) {
|
||||
return {
|
||||
isbn: data.identifiers.isbn_13 ? data.identifiers.isbn_13[0] : '',
|
||||
title: data.title,
|
||||
authors: data.authors ? data.authors.map(author => author.name) : [],
|
||||
publishedDate: data.publish_date,
|
||||
description: data.excerpts ? data.excerpts[0].text : 'No description available',
|
||||
cover: data.cover ? data.cover.large : ''
|
||||
};
|
||||
}
|
||||
|
||||
function formatArchiveData(data) {
|
||||
return {
|
||||
isbn: data.isbn ? data.isbn[0] : '',
|
||||
title: data.title,
|
||||
authors: data.creator,
|
||||
publishedDate: data.date,
|
||||
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, () => {
|
||||
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",
|
||||
"dependencies": {
|
||||
"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 name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ISBN Scanner</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
|
||||
<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>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Scan a Book ISBN</h1>
|
||||
@ -35,153 +19,17 @@
|
||||
<input type="file" id="barcode-input" accept="image/*">
|
||||
</div>
|
||||
<div id="book-info"></div>
|
||||
|
||||
<script>
|
||||
let selectedDeviceId;
|
||||
|
||||
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 {
|
||||
// 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>
|
||||
<div id="prompt">
|
||||
<p id="prompt-message"></p>
|
||||
<button id="confirm">Yes</button>
|
||||
<button id="edit-title">No, I'll add a title</button>
|
||||
</div>
|
||||
<div id="title-input">
|
||||
<label for="manual-title">Title:</label>
|
||||
<input type="text" id="manual-title">
|
||||
<button id="save-title">Save</button>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</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