first commit
This commit is contained in:
216
server.js
Normal file
216
server.js
Normal file
@@ -0,0 +1,216 @@
|
||||
import express from 'express';
|
||||
import multer from 'multer';
|
||||
import cors from 'cors';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { createClient } from "@libsql/client";
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 8080;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Directories
|
||||
const DATA_DIR = path.join(__dirname, 'data');
|
||||
const UPLOAD_DIR = path.join(__dirname, 'uploads');
|
||||
|
||||
// Ensure directories exist
|
||||
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true });
|
||||
|
||||
// Initialize LibSQL
|
||||
const db = createClient({
|
||||
url: "file:data/data.db"
|
||||
});
|
||||
|
||||
// Initialize Schema
|
||||
const initDB = async () => {
|
||||
try {
|
||||
await db.execute(`
|
||||
CREATE TABLE IF NOT EXISTS photos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
url TEXT NOT NULL,
|
||||
title TEXT,
|
||||
description TEXT,
|
||||
date TEXT,
|
||||
location TEXT,
|
||||
camera TEXT,
|
||||
lens TEXT,
|
||||
iso TEXT,
|
||||
aperture TEXT,
|
||||
shutter TEXT,
|
||||
focalLength TEXT,
|
||||
gps_lat REAL,
|
||||
gps_lng REAL,
|
||||
file_name TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
console.log("Database initialized");
|
||||
} catch (err) {
|
||||
console.error("Database initialization failed", err);
|
||||
}
|
||||
};
|
||||
|
||||
initDB();
|
||||
|
||||
// Multer Storage
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, UPLOAD_DIR);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
// Use timestamp to ensure unique filenames
|
||||
const ext = path.extname(file.originalname);
|
||||
const name = path.basename(file.originalname, ext).replace(/[^a-zA-Z0-9]/g, '-');
|
||||
cb(null, `${Date.now()}-${name}${ext}`);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({ storage });
|
||||
|
||||
// API Routes
|
||||
|
||||
// Get all photos
|
||||
app.get('/api/photos', async (req, res) => {
|
||||
try {
|
||||
const result = await db.execute("SELECT * FROM photos ORDER BY date DESC");
|
||||
// Map result rows to object format if needed, but LibSQL returns objects usually
|
||||
// We might need to reconstruct the nested 'settings' or 'gps' objects for the frontend
|
||||
const photos = result.rows.map(row => ({
|
||||
id: row.id,
|
||||
url: row.url,
|
||||
title: row.title,
|
||||
description: row.description,
|
||||
date: row.date,
|
||||
location: row.location,
|
||||
camera: row.camera,
|
||||
lens: row.lens,
|
||||
settings: {
|
||||
iso: row.iso,
|
||||
aperture: row.aperture,
|
||||
shutter: row.shutter,
|
||||
focalLength: row.focalLength
|
||||
},
|
||||
gps: (row.gps_lat && row.gps_lng) ? { lat: row.gps_lat, lng: row.gps_lng } : null,
|
||||
fileName: row.file_name
|
||||
}));
|
||||
res.json(photos);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Failed to fetch photos" });
|
||||
}
|
||||
});
|
||||
|
||||
// Upload a photo
|
||||
app.post('/api/photos', upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: 'No image uploaded' });
|
||||
}
|
||||
|
||||
const metadata = JSON.parse(req.body.metadata || '{}');
|
||||
const fileName = req.file.filename;
|
||||
const url = `/uploads/${fileName}`;
|
||||
|
||||
// Insert into DB
|
||||
await db.execute({
|
||||
sql: `INSERT INTO photos (
|
||||
url, title, description, date, location, camera, lens,
|
||||
iso, aperture, shutter, focalLength, gps_lat, gps_lng, file_name
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
args: [
|
||||
url,
|
||||
metadata.title || '',
|
||||
metadata.description || '',
|
||||
metadata.date || '',
|
||||
metadata.location || '',
|
||||
metadata.camera || '',
|
||||
metadata.lens || '',
|
||||
metadata.settings?.iso || '',
|
||||
metadata.settings?.aperture || '',
|
||||
metadata.settings?.shutter || '',
|
||||
metadata.settings?.focalLength || '',
|
||||
metadata.gps?.lat || null,
|
||||
metadata.gps?.lng || null,
|
||||
fileName
|
||||
]
|
||||
});
|
||||
|
||||
// We could fetch the inserted row, but for now just return success
|
||||
// Simulating the returned object
|
||||
const newPhoto = {
|
||||
id: Date.now(), // approximation, real ID is in DB
|
||||
url,
|
||||
...metadata
|
||||
};
|
||||
|
||||
res.status(201).json(newPhoto);
|
||||
} catch (error) {
|
||||
console.error("Upload error", error);
|
||||
res.status(500).json({ error: 'Upload failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete a photo
|
||||
app.delete('/api/photos/:id', async (req, res) => {
|
||||
const id = parseInt(req.params.id);
|
||||
|
||||
try {
|
||||
// Get filename first
|
||||
const result = await db.execute({
|
||||
sql: "SELECT file_name FROM photos WHERE id = ?",
|
||||
args: [id]
|
||||
});
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Photo not found' });
|
||||
}
|
||||
|
||||
const fileName = result.rows[0].file_name;
|
||||
|
||||
// Remove from DB
|
||||
await db.execute({
|
||||
sql: "DELETE FROM photos WHERE id = ?",
|
||||
args: [id]
|
||||
});
|
||||
|
||||
// Remove file
|
||||
if (fileName) {
|
||||
const filePath = path.join(UPLOAD_DIR, fileName);
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
console.error("Delete error", err);
|
||||
res.status(500).json({ error: "Delete failed" });
|
||||
}
|
||||
});
|
||||
|
||||
// Serve Uploads
|
||||
app.use('/uploads', express.static(UPLOAD_DIR));
|
||||
|
||||
// Serve Frontend (Production)
|
||||
const DIST_DIR = path.join(__dirname, 'dist');
|
||||
if (fs.existsSync(DIST_DIR)) {
|
||||
app.use(express.static(DIST_DIR));
|
||||
// SPA Fallback
|
||||
app.get(/.*/, (req, res) => {
|
||||
res.sendFile(path.join(DIST_DIR, 'index.html'));
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user