Files
HomeBase/routes/images.js
Spencer 854fd199bf
Some checks failed
Deploy to BeePC / deploy (push) Has been cancelled
feat: Add image deletion functionality before a specific date and update gallery UI
2026-02-12 16:14:06 -05:00

275 lines
6.9 KiB
JavaScript

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 });
}
});
/**
* POST /api/cleanup/before - Delete images before a specific date
*/
router.post('/cleanup/before', async (req, res) => {
try {
const { beforeDate } = req.body;
if (!beforeDate || typeof beforeDate !== 'string') {
return res.status(400).json({ success: false, error: 'beforeDate is required (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)' });
}
const normalizedInput = beforeDate.replace('T', ' ');
const normalized = normalizedInput.length <= 10
? `${normalizedInput} 00:00:00`
: (normalizedInput.length === 16 ? `${normalizedInput}:00` : normalizedInput);
const imagesToDelete = await database.getImagesBeforeDate(normalized);
let filesDeleted = 0;
for (const image of imagesToDelete) {
const deletedFile = await storage.deleteImageFile(image.file_path);
if (deletedFile) filesDeleted++;
}
const deletedFromDb = await database.deleteImagesBeforeDate(normalized);
res.json({
success: true,
cleanup: {
deletedFromDb,
filesDeleted,
beforeDate: normalized
}
});
} 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;