Files
portfolio-site/server/src/routes/Artworks.ts

207 lines
5.5 KiB
TypeScript

import path from 'path';
import fs from 'fs';
import express from 'express';
import multer from 'multer';
import {PrismaClient} from '../../generated/prisma';
const router = express.Router();
const prisma = new PrismaClient();
// Ensure uploads directory exists
const uploadsDir = path.resolve('uploads');
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir, {recursive: true});
}
// Configure multer for file uploads
const diskStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadsDir);
},
filename: (req, file, cb) => {
// Create unique filename with timestamp
const uniqueName = `${Date.now()}-${file.originalname}`;
cb(null, uniqueName);
}
});
// File validation
const fileFilter = (req: any, file: Express.Multer.File, cb: multer.FileFilterCallback) => {
// Accept only image files
const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
if (allowedMimeTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type. Only JPEG, PNG, GIF, and WebP images are allowed.'));
}
};
const upload = multer({
storage: diskStorage,
fileFilter: fileFilter,
limits: {
fileSize: 10 * 1024 * 1024 // 10MB limit
}
});
// GET all artworks
router.get('/', async (req, res) => {
try {
const artworks = await prisma.artworks.findMany({
select: {
id: true,
title: true,
filename: true,
mimetype: true,
size: true,
created_at: true,
updated_at: true
},
orderBy: {
created_at: 'desc'
}
});
return res.status(200).json(artworks);
} catch (error: any) {
console.error(error.message);
return res.status(500).json({status: 'error', message: 'Internal server error'});
}
});
// GET single artwork metadata
router.get('/:id', async (req, res) => {
const {id} = req.params;
try {
const artwork = await prisma.artworks.findUnique({
where: {id},
select: {
id: true,
title: true,
filename: true,
mimetype: true,
size: true,
created_at: true,
updated_at: true
}
});
if (!artwork) {
return res.status(404).json({status: 'error', message: 'Artwork not found'});
}
return res.status(200).json(artwork);
} catch (error: any) {
console.error(error.message);
return res.status(500).json({status: 'error', message: 'Internal server error'});
}
});
// GET artwork image file
router.get('/:id/image', async (req, res) => {
const {id} = req.params;
try {
const artwork = await prisma.artworks.findUnique({
where: {id},
select: {filename: true, mimetype: true}
});
if (!artwork) {
return res.status(404).json({status: 'error', message: 'Artwork not found'});
}
const filePath = path.join(uploadsDir, artwork.filename);
// Check if file exists
if (!fs.existsSync(filePath)) {
return res.status(404).json({status: 'error', message: 'Image file not found'});
}
// Set caching headers for better performance
res.setHeader('Content-Type', artwork.mimetype);
res.setHeader('Cache-Control', 'public, max-age=31536000'); // Cache for 1 year
res.setHeader('ETag', artwork.filename); // Use filename as ETag
// Send file
return res.sendFile(filePath);
} catch (error: any) {
console.error(error.message);
return res.status(500).json({status: 'error', message: 'Internal server error'});
}
});
// POST new artwork
router.post('/', upload.single('image'), async (req, res) => {
if (!req.file) {
return res.status(400).json({status: 'error', message: 'No image file uploaded'});
}
if (!req.body.title) {
// Cleanup uploaded file if validation fails
fs.unlinkSync(req.file.path);
return res.status(400).json({status: 'error', message: 'Title is required'});
}
try {
const artwork = await prisma.artworks.create({
data: {
title: req.body.title,
filename: req.file.filename,
mimetype: req.file.mimetype,
size: req.file.size
}
});
return res.status(201).json({
id: artwork.id,
title: artwork.title,
filename: artwork.filename,
mimetype: artwork.mimetype,
size: artwork.size,
created_at: artwork.created_at,
updated_at: artwork.updated_at
});
} catch (error: any) {
// Cleanup uploaded file if database operation fails
fs.unlinkSync(req.file.path);
console.error(error.message);
if (error.code === 'P2002') {
return res.status(409).json({status: 'error', message: 'An artwork with this title already exists'});
}
return res.status(500).json({status: 'error', message: 'Internal server error'});
}
});
// DELETE artwork
router.delete('/:id', async (req, res) => {
const {id} = req.params;
try {
const artwork = await prisma.artworks.findUnique({
where: {id},
select: {filename: true}
});
if (!artwork) {
return res.status(404).json({status: 'error', message: 'Artwork not found'});
}
// Delete from database
await prisma.artworks.delete({where: {id}});
// Delete file from disk
const filePath = path.join(uploadsDir, artwork.filename);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
return res.status(200).json({status: 'success', message: 'Artwork deleted'});
} catch (error: any) {
console.error(error.message);
return res.status(500).json({status: 'error', message: 'Internal server error'});
}
});
export default router;