stl-storage/database.js
kris 3dff6b00d4 Initial commit: STL Storage Application
- Complete web-based STL file storage and 3D viewer
- Express.js backend with SQLite database
- Interactive Three.js 3D viewer with orbit controls
- File upload with drag-and-drop support
- Security features: rate limiting, input validation, helmet
- Container deployment with Docker/Podman
- Production-ready configuration management
- Comprehensive logging and monitoring

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-07 16:18:58 +00:00

244 lines
6.9 KiB
JavaScript

const sqlite3 = require('sqlite3').verbose();
const path = require('path');
const fs = require('fs').promises;
class STLDatabase {
constructor(dbPath = './stl_storage.db', uploadDir = './uploads') {
// Use data directory if available (for containers)
if (process.env.NODE_ENV === 'production' && require('fs').existsSync('./data')) {
this.dbPath = './data/stl_storage.db';
} else {
this.dbPath = dbPath;
}
this.uploadDir = uploadDir;
this.db = null;
}
async initialize() {
await this.ensureDirectories();
await this.connectDatabase();
await this.initializeSchema();
}
async ensureDirectories() {
try {
await fs.access(this.uploadDir);
} catch {
await fs.mkdir(this.uploadDir, { recursive: true });
}
}
async connectDatabase() {
return new Promise((resolve, reject) => {
this.db = new sqlite3.Database(this.dbPath, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
async initializeSchema() {
const schema = await fs.readFile('./schema.sql', 'utf8');
return new Promise((resolve, reject) => {
this.db.exec(schema, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
async addSTLFile(fileData) {
const {
filename,
originalName,
filePath,
fileSize,
description = null,
tags = null,
printSettings = null,
dimensions = null
} = fileData;
return new Promise((resolve, reject) => {
const stmt = this.db.prepare(`
INSERT INTO stl_files
(filename, original_name, file_path, file_size, description, tags, print_settings, dimensions)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run([
filename,
originalName,
filePath,
fileSize,
description,
tags,
printSettings ? JSON.stringify(printSettings) : null,
dimensions ? JSON.stringify(dimensions) : null
], function(err) {
if (err) {
reject(err);
} else {
resolve(this.lastID);
}
});
stmt.finalize();
});
}
async getSTLFile(id) {
return new Promise((resolve, reject) => {
this.db.get(
'SELECT * FROM stl_files WHERE id = ?',
[id],
(err, row) => {
if (err) {
reject(err);
} else {
resolve(row ? this.parseRow(row) : null);
}
}
);
});
}
async getAllSTLFiles(limit = 50, offset = 0) {
return new Promise((resolve, reject) => {
this.db.all(
'SELECT * FROM stl_files ORDER BY upload_date DESC LIMIT ? OFFSET ?',
[limit, offset],
(err, rows) => {
if (err) {
reject(err);
} else {
resolve(rows.map(row => this.parseRow(row)));
}
}
);
});
}
async searchSTLFiles(query) {
return new Promise((resolve, reject) => {
this.db.all(
`SELECT * FROM stl_files
WHERE filename LIKE ? OR original_name LIKE ? OR description LIKE ? OR tags LIKE ?
ORDER BY upload_date DESC`,
[`%${query}%`, `%${query}%`, `%${query}%`, `%${query}%`],
(err, rows) => {
if (err) {
reject(err);
} else {
resolve(rows.map(row => this.parseRow(row)));
}
}
);
});
}
async updateSTLFile(id, updates) {
const allowedFields = ['description', 'tags', 'print_settings', 'dimensions'];
const updateFields = [];
const values = [];
for (const [key, value] of Object.entries(updates)) {
if (allowedFields.includes(key)) {
updateFields.push(`${key} = ?`);
if (key === 'print_settings' || key === 'dimensions') {
values.push(value ? JSON.stringify(value) : null);
} else {
values.push(value);
}
}
}
if (updateFields.length === 0) {
throw new Error('No valid fields to update');
}
values.push(id);
return new Promise((resolve, reject) => {
this.db.run(
`UPDATE stl_files SET ${updateFields.join(', ')} WHERE id = ?`,
values,
function(err) {
if (err) {
reject(err);
} else {
resolve(this.changes > 0);
}
}
);
});
}
async deleteSTLFile(id) {
const file = await this.getSTLFile(id);
if (!file) {
return false;
}
try {
await fs.unlink(file.file_path);
} catch (err) {
console.warn(`Could not delete file ${file.file_path}:`, err.message);
}
return new Promise((resolve, reject) => {
this.db.run(
'DELETE FROM stl_files WHERE id = ?',
[id],
function(err) {
if (err) {
reject(err);
} else {
resolve(this.changes > 0);
}
}
);
});
}
parseRow(row) {
const parsed = { ...row };
if (parsed.print_settings) {
try {
parsed.print_settings = JSON.parse(parsed.print_settings);
} catch {
parsed.print_settings = null;
}
}
if (parsed.dimensions) {
try {
parsed.dimensions = JSON.parse(parsed.dimensions);
} catch {
parsed.dimensions = null;
}
}
return parsed;
}
async close() {
return new Promise((resolve) => {
this.db.close((err) => {
if (err) {
console.error('Error closing database:', err);
}
resolve();
});
});
}
}
module.exports = STLDatabase;