From 225cae73b52c7d62beca4ca2f0d754252d8288a3 Mon Sep 17 00:00:00 2001 From: rishi Date: Sun, 1 Dec 2024 17:59:29 +0000 Subject: [PATCH] Images now uploading --- client/src/components/Editor.jsx | 2 +- server/src/routes/images.js | 112 +++++++++++++++++++++++-------- 2 files changed, 85 insertions(+), 29 deletions(-) diff --git a/client/src/components/Editor.jsx b/client/src/components/Editor.jsx index 2f51e5e..535d323 100644 --- a/client/src/components/Editor.jsx +++ b/client/src/components/Editor.jsx @@ -192,7 +192,7 @@ export default function Editor() { placeholder="Add tags (press Enter)" value={tagInput} onChange={(e) => setTagInput(e.target.value)} - onKeyPress={handleAddTag} + onKeyDown={handleAddTag} /> diff --git a/server/src/routes/images.js b/server/src/routes/images.js index 7f34611..072b982 100644 --- a/server/src/routes/images.js +++ b/server/src/routes/images.js @@ -1,9 +1,10 @@ -// File: src/routes/images.js +// src/routes/images.js import { pipeline } from 'stream/promises'; import { createWriteStream } from 'fs'; import { join } from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; +import slugify from 'slugify'; import { db } from '../db.js'; const __filename = fileURLToPath(import.meta.url); @@ -19,7 +20,88 @@ const authenticate = async (request, reply) => { }; export default async function imageRoutes(fastify) { - // Upload image for a post + // Get all images + fastify.get('/api/images', { + onRequest: [authenticate], + handler: async (request, reply) => { + try { + const images = await db.any( + `SELECT i.* FROM images i + JOIN posts p ON i.post_id = p.id + WHERE p.author_id = $1 + ORDER BY i.uploaded_at DESC`, + [request.user.id] + ); + reply.send(images); + } catch (err) { + fastify.log.error(err); + reply.code(500).send({ error: 'Internal server error' }); + } + } + }); + + // Upload image without post association + fastify.post('/api/images/upload', { + onRequest: [authenticate], + handler: async (request, reply) => { + try { + // Handle file upload + const data = await request.file(); + + if (!data) { + reply.code(400).send({ error: 'No file uploaded' }); + return; + } + + // Validate file type + const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; + if (!allowedMimeTypes.includes(data.mimetype)) { + reply.code(400).send({ error: 'Invalid file type' }); + return; + } + + // Create unique filename + const fileExt = data.filename.split('.').pop(); + const filename = `${Date.now()}-${Math.random().toString(36).slice(2)}.${fileExt}`; + const uploadDir = join(__dirname, '../../uploads'); + const filepath = join(uploadDir, filename); + + // Save file to disk + await pipeline(data.file, createWriteStream(filepath)); + + // First create a draft post to associate the image with + const tempTitle = `Image Upload ${new Date().toISOString()}`; + const post = await db.one( + `INSERT INTO posts (title, slug, markdown_content, compiled_content, status, author_id) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id`, + [ + tempTitle, + slugify(tempTitle, { lower: true }), + '', + '', + 'draft', + request.user.id + ] + ); + + // Save image record in database + const image = await db.one( + `INSERT INTO images (filename, url, post_id) + VALUES ($1, $2, $3) + RETURNING *`, + [filename, `/uploads/${filename}`, post.id] + ); + + reply.code(201).send(image); + } catch (err) { + fastify.log.error(err); + reply.code(500).send({ error: 'Internal server error' }); + } + } + }); + + // Upload image for a specific post fastify.post('/api/posts/:postId/images', { onRequest: [authenticate], handler: async (request, reply) => { @@ -99,9 +181,6 @@ export default async function imageRoutes(fastify) { // Delete from database await db.none('DELETE FROM images WHERE id = $1', [id]); - // We could also delete the file from disk here, but we might want to keep it - // for backup purposes or to handle undo operations - reply.code(204).send(); } catch (err) { fastify.log.error(err); @@ -109,27 +188,4 @@ export default async function imageRoutes(fastify) { } } }); - - // Get images for a post - fastify.get('/api/posts/:postId/images', { - onRequest: [authenticate], - handler: async (request, reply) => { - const { postId } = request.params; - - try { - const images = await db.any( - `SELECT i.* FROM images i - JOIN posts p ON i.post_id = p.id - WHERE p.id = $1 AND p.author_id = $2 - ORDER BY i.uploaded_at DESC`, - [postId, request.user.id] - ); - - reply.send(images); - } catch (err) { - fastify.log.error(err); - reply.code(500).send({ error: 'Internal server error' }); - } - } - }); } \ No newline at end of file