feat: Implement image fetching and storage system
Some checks failed
Deploy to BeePC / deploy (push) Has been cancelled
Some checks failed
Deploy to BeePC / deploy (push) Has been cancelled
- Add image-fetcher module for downloading and saving images from various sources. - Create storage module for managing image files, including downloading, verifying integrity, and cleaning up orphaned files. - Develop gallery HTML page for displaying images with sorting and filtering options. - Set up RESTful API routes for image management, including fetching, adding tags, and deleting images. - Introduce setup script for initializing the database and configuring image sources. - Implement a batch process for verifying image integrity and cleaning up old images. - Add setup batch script for easy installation and configuration of the image storage system.
This commit is contained in:
235
routes/images.js
Normal file
235
routes/images.js
Normal file
@@ -0,0 +1,235 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const database = require('../lib/database');
|
||||
const storage = require('../lib/storage');
|
||||
const imageFetcher = require('../lib/image-fetcher');
|
||||
|
||||
/**
|
||||
* GET /api/images - Get all images with optional filtering
|
||||
*/
|
||||
router.get('/images', async (req, res) => {
|
||||
try {
|
||||
const { tag, sourceUrl, page = 1, pageSize = 50, sort = 'fetched_at', order = 'DESC' } = req.query;
|
||||
|
||||
const limit = parseInt(pageSize);
|
||||
const offset = (parseInt(page) - 1) * limit;
|
||||
|
||||
const images = await database.getImages({
|
||||
tag,
|
||||
sourceUrl,
|
||||
limit,
|
||||
offset,
|
||||
sortBy: sort,
|
||||
order
|
||||
});
|
||||
|
||||
// Get tags for each image
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
images[i].tags = await database.getImageTags(images[i].id);
|
||||
}
|
||||
|
||||
const total = await database.getImageCount(tag);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
images,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
pageSize: limit,
|
||||
total,
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/images/:id - Get a specific image
|
||||
*/
|
||||
router.get('/images/:id', async (req, res) => {
|
||||
try {
|
||||
const image = await database.getImage(req.params.id);
|
||||
|
||||
if (!image) {
|
||||
return res.status(404).json({ success: false, error: 'Image not found' });
|
||||
}
|
||||
|
||||
image.tags = await database.getImageTags(image.id);
|
||||
|
||||
res.json({ success: true, image });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/images/:id/download - Download image file
|
||||
*/
|
||||
router.get('/images/:id/download', async (req, res) => {
|
||||
try {
|
||||
const image = await database.getImage(req.params.id);
|
||||
|
||||
if (!image) {
|
||||
return res.status(404).json({ success: false, error: 'Image not found' });
|
||||
}
|
||||
|
||||
const buffer = await storage.getImageBuffer(image.file_path);
|
||||
res.contentType(image.mime_type);
|
||||
res.send(buffer);
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/images - Fetch and save a new image
|
||||
*/
|
||||
router.post('/images', async (req, res) => {
|
||||
try {
|
||||
const { source_url, tags = [] } = req.body;
|
||||
|
||||
if (!source_url) {
|
||||
return res.status(400).json({ success: false, error: 'source_url is required' });
|
||||
}
|
||||
|
||||
const result = await imageFetcher.fetchImage(source_url, tags);
|
||||
|
||||
if (!result.success) {
|
||||
return res.status(400).json({ success: false, error: result.error });
|
||||
}
|
||||
|
||||
const image = await database.getImage(result.imageId);
|
||||
image.tags = await database.getImageTags(image.id);
|
||||
|
||||
res.status(201).json({ success: true, image });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/images/:id/tags - Add tags to an image
|
||||
*/
|
||||
router.post('/images/:id/tags', async (req, res) => {
|
||||
try {
|
||||
const { tags = [] } = req.body;
|
||||
|
||||
if (!Array.isArray(tags) || tags.length === 0) {
|
||||
return res.status(400).json({ success: false, error: 'tags must be a non-empty array' });
|
||||
}
|
||||
|
||||
const image = await database.getImage(req.params.id);
|
||||
if (!image) {
|
||||
return res.status(404).json({ success: false, error: 'Image not found' });
|
||||
}
|
||||
|
||||
await database.addTags(image.id, tags);
|
||||
|
||||
image.tags = await database.getImageTags(image.id);
|
||||
|
||||
res.json({ success: true, image });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/images/:id - Delete an image
|
||||
*/
|
||||
router.delete('/images/:id', async (req, res) => {
|
||||
try {
|
||||
const image = await database.getImage(req.params.id);
|
||||
|
||||
if (!image) {
|
||||
return res.status(404).json({ success: false, error: 'Image not found' });
|
||||
}
|
||||
|
||||
// Delete file
|
||||
await storage.deleteImageFile(image.file_path);
|
||||
|
||||
// Delete database record
|
||||
await database.deleteImage(image.id);
|
||||
|
||||
res.json({ success: true, message: 'Image deleted' });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/tags - Get all available tags
|
||||
*/
|
||||
router.get('/tags', async (req, res) => {
|
||||
try {
|
||||
const tags = await database.getAllTags();
|
||||
res.json({ success: true, tags });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/stats - Get storage and image statistics
|
||||
*/
|
||||
router.get('/stats', async (req, res) => {
|
||||
try {
|
||||
const storageStats = await storage.getStorageStats();
|
||||
const imageCount = await database.getImageCount();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
stats: {
|
||||
imageCount,
|
||||
...storageStats
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/verify - Verify all images for corruption
|
||||
*/
|
||||
router.post('/verify', async (req, res) => {
|
||||
try {
|
||||
const result = await imageFetcher.verifyAllImages();
|
||||
res.json({ success: true, result });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/cleanup - Clean up old images
|
||||
*/
|
||||
router.post('/cleanup', async (req, res) => {
|
||||
try {
|
||||
const { daysOld = 30 } = req.body;
|
||||
|
||||
const deleted = await database.cleanupOldImages(daysOld);
|
||||
const orphaned = await storage.cleanupOrphanedFiles(database.db);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
cleanup: {
|
||||
deletedFromDb: deleted,
|
||||
orphanedFilesRemoved: orphaned.cleaned
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/fetcher/status - Get fetcher status
|
||||
*/
|
||||
router.get('/fetcher/status', (req, res) => {
|
||||
const status = imageFetcher.getStatus();
|
||||
res.json({ success: true, status });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user