Image & Storage Handling
Blogify leverages Supabase Storage to handle all media assets. The system is designed for high-performance direct-to-storage uploads from the client, reducing the load on the FastAPI backend while ensuring secure access through Row Level Security (RLS).
Storage Architecture
Images are stored in a public bucket named images. While the bucket provides public read access for viewing blog posts, write operations are strictly protected.
- Direct Uploads: The frontend communicates directly with Supabase Storage to upload files.
- URL Persistence: Only the public URL of the uploaded asset is sent to the FastAPI backend to be stored in the PostgreSQL database.
- Security: Uploads require a valid Supabase JWT. The storage bucket is configured with RLS policies that ensure users can only upload files if they are authenticated.
Image Types
Post Featured Images
Every blog post can have a single featured image that appears at the top of the article and in the community feed.
- Usage: Selected via the file input on the "New Post" or "Edit Post" pages.
- Storage Path:
images/{timestamp}-{random-id}.{ext}
In-Content Images (Markdown)
Blogify supports embedding images directly within the Markdown body of a post.
- Workflow: When creating or editing a post, clicking the image upload button inserts the specialized Markdown syntax
at your current cursor position. - Rendering: The frontend uses
react-markdownwithremark-unwrap-imagesto ensure these images are displayed responsively within the article flow.
User Avatars
User profile pictures are handled similarly to post images. These are displayed on the user's public profile and next to their name on posts they have authored.
Developer Reference
The uploadImage Function
The core utility for handling media is the uploadImage function located in src/lib/api.ts.
Signature
async function uploadImage(file: File): Promise<string>
Parameters
| Parameter | Type | Description |
| :--- | :--- | :--- |
| file | File | The browser File object (usually from an <input type="file">). |
Returns
Promise<string>: Returns the public URL of the uploaded image upon success.
Error Handling
The function will throw an error if:
- The user is not logged in (session is missing).
- The file exceeds bucket size limits.
- Network issues prevent communication with Supabase.
Usage Example: Featured Image Upload
When a user submits the post form, the featured image is processed before the post metadata is sent to the API:
import { uploadImage, createPost } from '@/lib/api';
async function handleSubmit(imageFile: File, postData: any) {
let imageUrl = '';
if (imageFile) {
// 1. Upload to Supabase Storage
imageUrl = await uploadImage(imageFile);
}
// 2. Send the resulting URL to the Backend API
await createPost({
...postData,
image_url: imageUrl
});
}
Usage Example: Markdown Insertion
The following pattern is used within the editor to insert images into the post body dynamically:
const handleContentImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
try {
const url = await uploadImage(file);
const imageMarkdown = `\n\n`;
// Logic to insert 'imageMarkdown' into the textarea at the cursor position
setContent((prev) => prev + imageMarkdown);
} catch (err) {
alert('Failed to upload image');
}
};
Best Practices
- File Types: The UI currently accepts standard web formats (JPG, PNG, WebP).
- Naming: The system automatically generates unique filenames using
Date.now()combined with a random string to prevent filename collisions in the storage bucket. - Persistence: If a user uploads an image but does not save the post, the image remains in Supabase Storage. (Maintenance scripts can be implemented to prune orphaned assets if required).