This endpoint requires authentication. Users can only access their own media files.
Overview
Retrieve all media files from the user’s Media Vault. This endpoint automatically syncs storage files with the database, backfilling any missing records.
Query Parameters
Filter media by source category.Options:
all - Return all media
smart-caption - Only Smart Captions
babecock - Only Babecock Studio media
hypnococks - Only HypnoCocks media
upload - Only user uploads
Filter media by creation date.Options:
all - All time
today - Today only
week - Last 7 days
month - Current month
year - Current year
Search media by filename, original name, caption, or character name. Case-insensitive partial matching.
Storage Synchronization
Automatic Backfill
This endpoint performs automatic synchronization between Supabase Storage and the database:
- Fetch existing database records for the user
- List all files from the following storage folders:
Captions/ (Smart Captions)
Babecocks/ (Babecock Studio)
HypnoCocks/ (HypnoCocks)
Uploads/ (General uploads)
- Backfill missing records: Any files found in storage but not in the database are automatically added
- Queue enrichment: Newly backfilled items are queued for metadata enrichment (dimensions, character detection, etc.)
{
userId: "user_123",
fileName: "file.jpg",
originalName: "file.jpg",
fileUrl: "https://...supabase.co/.../file.jpg",
filePath: "users/user_123/Captions/file.jpg",
fileSize: 0, // Size from storage metadata if available
mimeType: "application/octet-stream", // Or from storage metadata
source: "smart-caption", // Derived from folder name
width: 0,
height: 0,
isShared: false,
shareToCommunity: false,
subreddit: null,
enrichmentStatus: "pending",
metadata: {
tool: "smart-caption",
backfilledAt: "2024-03-08T12:34:56.789Z"
}
}
Response
Array of media file objects
Total number of files returned after filtering
Count of files in each categoryProperties:
smart-caption (number)
babecock (number)
hypnococks (number)
upload (number)
File Object Schema
Database ID of the media item (for bulk operations)
Display name of the file (friendly name or filename)
Original filename when uploaded
Source category: smart-caption, babecock, hypnococks, or upload
ISO 8601 timestamp of when the file was created
ISO 8601 timestamp of creation (same as lastModified)
Public URL to access the file
Storage path: users/{userId}/{Folder}/{filename}
Source subreddit if the media was fetched from Reddit
Image/video width in pixels (if enriched)
Image/video height in pixels (if enriched)
Whether the media has a share link generated
Unique share code if the media is shared
Detected character or model name (if enriched)
Status of metadata enrichment: pending, processing, completed, or failed
ISO 8601 timestamp of when enrichment was completed
Success Response Example
{
"files": [
{
"id": 456,
"name": "caption-1709856234567-beach_photo.jpg",
"originalName": "beach_photo.jpg",
"category": "smart-caption",
"size": 2048576,
"lastModified": "2024-03-08T10:30:34.567Z",
"created_at": "2024-03-08T10:30:34.567Z",
"url": "https://example.supabase.co/storage/v1/object/public/user-media/users/user_123/Captions/caption-1709856234567-beach_photo.jpg",
"path": "users/user_123/Captions/caption-1709856234567-beach_photo.jpg",
"subreddit": "EarthPorn",
"width": 1920,
"height": 1080,
"isShared": true,
"shareCode": "abc123def456",
"characterName": null,
"enrichmentStatus": "completed",
"enrichedAt": "2024-03-08T10:31:05.123Z"
},
{
"id": 457,
"name": "babecock-1709856300000-composite.png",
"originalName": "composite.png",
"category": "babecock",
"size": 3145728,
"lastModified": "2024-03-08T11:45:00.000Z",
"created_at": "2024-03-08T11:45:00.000Z",
"url": "https://example.supabase.co/storage/v1/object/public/user-media/users/user_123/Babecocks/babecock-1709856300000-composite.png",
"path": "users/user_123/Babecocks/babecock-1709856300000-composite.png",
"subreddit": null,
"width": 2560,
"height": 1440,
"isShared": false,
"shareCode": null,
"characterName": "Riley Reid",
"enrichmentStatus": "completed",
"enrichedAt": "2024-03-08T11:45:30.456Z"
}
],
"totalCount": 2,
"categories": {
"smart-caption": 1,
"babecock": 1,
"hypnococks": 0,
"upload": 0
}
}
Error Responses
500 Internal Server Error
{
"message": "Failed to fetch user media"
}
Example Usage
cURL
# Get all media
curl -X GET "https://your-domain.com/api/user/media" \
-H "Cookie: connect.sid=your-session-cookie"
# Filter by category
curl -X GET "https://your-domain.com/api/user/media?category=smart-caption" \
-H "Cookie: connect.sid=your-session-cookie"
# Search and filter by date
curl -X GET "https://your-domain.com/api/user/media?search=beach&dateRange=week" \
-H "Cookie: connect.sid=your-session-cookie"
JavaScript (Fetch API)
// Fetch all media
const response = await fetch('/api/user/media', {
credentials: 'include'
});
const data = await response.json();
console.log(`Total files: ${data.totalCount}`);
console.log('Files:', data.files);
// Filter by category
const smartCaptionsResponse = await fetch('/api/user/media?category=smart-caption', {
credentials: 'include'
});
const smartCaptions = await smartCaptionsResponse.json();
React Example with Filtering
import { useState, useEffect } from 'react';
function MediaGallery() {
const [media, setMedia] = useState([]);
const [category, setCategory] = useState('all');
const [search, setSearch] = useState('');
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchMedia = async () => {
setLoading(true);
const params = new URLSearchParams();
if (category !== 'all') params.append('category', category);
if (search) params.append('search', search);
const response = await fetch(`/api/user/media?${params}`, {
credentials: 'include'
});
const data = await response.json();
setMedia(data.files);
setLoading(false);
};
fetchMedia();
}, [category, search]);
return (
<div>
<div>
<select value={category} onChange={(e) => setCategory(e.target.value)}>
<option value="all">All Categories</option>
<option value="smart-caption">Smart Captions</option>
<option value="babecock">Babecock</option>
<option value="hypnococks">HypnoCocks</option>
<option value="upload">Uploads</option>
</select>
<input
type="text"
placeholder="Search..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
{loading ? (
<p>Loading...</p>
) : (
<div className="grid">
{media.map((file) => (
<div key={file.id}>
<img src={file.url} alt={file.originalName} />
<p>{file.originalName}</p>
<p>{file.category}</p>
</div>
))}
</div>
)}
</div>
);
}
Notes
- Media is sorted by creation date in descending order (newest first)
- The endpoint performs automatic backfill on every request to keep database in sync with storage
- Metadata files (
.json) are automatically excluded from results
- Only valid media files (jpg, jpeg, png, gif, webp, mp4, mov, webm) are included
- Enrichment is queued automatically for backfilled items but happens asynchronously