Files
deardiary/todo/gallery.md
lotherk 0bdd71a4ed feat: v0.1.0 - geolocation capture, calendar, search, Starlight docs site
- Automatic browser geolocation capture on event creation
- Reverse geocoding via Nominatim API for place names
- Full-text search with SQLite FTS5
- Calendar view for browsing past entries
- DateNavigator component for day navigation
- SearchModal with Ctrl+K shortcut
- QuickAddWidget with Ctrl+J shortcut
- Starlight documentation site with GitHub Pages deployment
- Multiple AI provider support (Groq, OpenAI, Anthropic, Ollama, LM Studio)
- Multi-user registration support

BREAKING: Events now include latitude/longitude/placeName fields
2026-03-27 02:27:55 +00:00

538 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Media Gallery Feature for DearDiary
## Overview
This document outlines the comprehensive feature specification for adding media gallery capabilities to DearDiary - an AI-powered daily journaling app. The gallery will allow users to view, manage, and organize all their photos and voice recordings in one centralized location.
## Feature Description
### Core Functionality
1. **Media Grid View**
- Responsive masonry or grid layout displaying all media items
- Support for both photos and voice recordings
- Visual distinction between media types (photo thumbnails vs audio waveform icons)
- Lazy loading for performance with large collections
- Infinite scroll pagination
2. **Audio Player**
- Inline audio player for voice recordings
- Play/pause controls
- Progress bar with seek functionality
- Duration display
- Waveform visualization (optional enhancement)
3. **Filtering & Organization**
- Filter by media type (photos, voice recordings, all)
- Filter by date range (today, this week, this month, custom range)
- Search by date
- Sort by date (newest/oldest)
4. **Thumbnail Generation**
- Automatic thumbnail generation for photos
- Multiple sizes: small (150px), medium (300px), large (600px)
- Server-side processing using sharp or similar library
- Cache thumbnails for performance
5. **Lightbox View**
- Full-screen photo viewer
- Navigation between photos (previous/next)
- Zoom functionality
- Photo metadata display (EXIF data)
- Close on escape key or click outside
6. **Storage Management**
- Display total storage used
- Show individual file sizes
- Bulk delete functionality
- Delete confirmation dialogs
- Storage quota warnings
7. **Media Metadata**
- EXIF data extraction for photos (camera, date taken, location, etc.)
- Audio duration and format info
- File size and dimensions
- Creation date
8. **Privacy Considerations**
- All media stored locally or on user's own object storage
- No third-party media processing
- Optional encryption at rest
- Clear deletion removes all related data
---
## Storage Architecture
### Option 1: Local File Storage (Recommended for Self-Hosted)
```
/data/media/
/{userId}/
/{date}/
/photos/
{eventId}.jpg # Original
{eventId}_thumb.jpg # Thumbnail
{eventId}_medium.jpg # Medium size
/voice/
{eventId}.webm # Voice recording
```
**Pros:**
- Simple to implement
- No additional costs
- Full control over data
- Easy backup
**Cons:**
- Requires server-side file management
- No CDN for fast delivery
- Storage limited to server capacity
**Implementation:**
- Use `sharp` library for image processing
- Generate thumbnails on upload or on-demand
- Serve via static file middleware
### Option 2: Object Storage (S3-compatible)
**Pros:**
- Scalable storage
- Built-in CDN integration
- High availability
- Offload bandwidth
**Cons:**
- Additional complexity and cost
- Requires S3-compatible service
- More complex authentication
**Recommendation:** Start with local storage for v1, design for S3 compatibility in v2.
---
## Database Schema Changes
### New Media Model
```prisma
model Media {
id String @id @default(uuid())
userId String
eventId String?
type String // "photo" | "voice"
fileName String
originalName String
mimeType String
fileSize Int // bytes
duration Int? // seconds (for audio)
width Int? // pixels
height Int? // pixels
thumbnailPath String?
mediumPath String?
exifData String? // JSON string
storageType String @default("local")
storageKey String? // S3 key if using object storage
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
event Event? @relation(fields: [eventId], references: [id], onDelete: SetNull)
@@index([userId, type])
@@index([userId, createdAt])
@@index([eventId])
}
```
### Updated Event Model
```prisma
model Event {
id String @id @default(uuid())
userId String
date String
type String // "event" | "text" | "photo" | "voice" | "health"
content String
metadata String?
mediaId String? // Link to Media
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
media Media? @relation(fields: [mediaId], references: [id], onDelete: SetNull)
@@index([userId, date])
@@index([date])
@@index([type])
}
```
### Storage Statistics (Optional)
```prisma
model StorageStats {
userId String @id
totalBytes Int @default(0)
photoBytes Int @default(0)
voiceBytes Int @default(0)
photoCount Int @default(0)
voiceCount Int @default(0)
lastUpdated DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
```
---
## API Endpoints Needed
### Media Management
```
// Upload media (photo or voice)
POST /api/v1/media/upload
Body: multipart/form-data
- file: File
- type: "photo" | "voice"
- date: string (YYYY-MM-DD)
- eventId?: string (optional link to existing event)
- metadata?: object (optional additional metadata)
// Get media item by ID
GET /api/v1/media/:id
// Delete media item
DELETE /api/v1/media/:id
// Bulk delete media
POST /api/v1/media/bulk-delete
Body: { ids: string[] }
// Get media thumbnail
GET /api/v1/media/:id/thumbnail/:size
- size: "small" | "medium" | "large"
// Stream media file
GET /api/v1/media/:id/stream
// Get EXIF data
GET /api/v1/media/:id/exif
// Get storage usage stats
GET /api/v1/media/stats
// List all media with filters
GET /api/v1/media
Query params:
- type?: "photo" | "voice" | "all"
- startDate?: string (YYYY-MM-DD)
- endDate?: string (YYYY-MM-DD)
- page?: number
- limit?: number
- sort?: "newest" | "oldest"
```
### Integration with Existing Events
```
// Create event with media
POST /api/v1/events
Body: {
date, type, content, metadata,
media: { type, file }
}
// Add media to existing event
POST /api/v1/events/:id/media
Body: multipart/form-data with file
// Get event with media
GET /api/v1/events/:id
Response includes linked media
```
---
## UI/UX Design Patterns
### Gallery Page Layout
```
┌─────────────────────────────────────────────────────┐
│ DearDiary [Settings] │
├─────────────────────────────────────────────────────┤
│ [Dashboard] [Today] [History] [Diary] [Gallery] │
├─────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────┐ │
│ │ 📷 Photos 🎤 Voice | Jan 2026 ▼ │ │
│ │ Total: 2.3 GB | 156 photos | 42 voice │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ 📷 │ │ 📷 │ │ 🎤 │ │ 📷 │ │ 📷 │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ 📷 │ │ 🎤 │ │ 📷 │ │ 📷 │ │ 🎤 │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
│ [Load More...] │
└─────────────────────────────────────────────────────┘
```
### Filter Bar Components
- **Type Toggle**: Segmented button control (All | Photos | Voice)
- **Date Picker**: Month/Year dropdown with custom range option
- **Search**: Date input or quick filters (Today, This Week, This Month, Year)
### Photo Card Component
```
┌─────────────────┐
│ │
│ [Thumbnail] │
│ │
├─────────────────┤
│ Jan 15, 2026 │
│ 2.4 MB • 4032×3024 │
└─────────────────┘
```
### Voice Card Component
```
┌─────────────────────────────┐
│ 🎤 Voice Recording │
│ ━━━━━━━━━●━━━━━━━━━━━━━ │
│ 0:45 / 2:30 │
│ Jan 15, 2026 • 1.2 MB │
└─────────────────────────────┘
```
### Lightbox Component
```
┌─────────────────────────────────────────────────────┐
│ ← │ 📷 Photo Title │ ⋮ │ → │
│ │ │ │
│ │ │ │
│ │ [Full Size Image] │ │
│ │ │ │
│ │ │ │
├─────┴──────────────────────────┴────────────────┤
│ 📅 Jan 15, 2026 📷 4032×3024 📏 2.4 MB │
│ 📷 iPhone 15 Pro 📍 San Francisco │
└─────────────────────────────────────────────────────┘
```
### Storage Management Panel (Settings or Modal)
```
┌─────────────────────────────────────────┐
│ Storage Usage │
│ │
│ ████████████████████░░░░░░░░░ 2.3/10GB│
│ │
│ 📷 Photos: 1.8 GB (156 files) │
│ 🎤 Voice: 500 MB (42 files) │
│ │
│ [Delete Old Media] │
│ [Download All] │
│ [Clear All Media] │
└─────────────────────────────────────────┘
```
### Design Tokens (Tailwind)
- **Background**: `bg-slate-900`
- **Cards**: `bg-slate-800`
- **Borders**: `border-slate-700`
- **Primary**: `text-purple-400`
- **Muted**: `text-slate-400`
- **Hover**: `hover:bg-slate-700`
- **Active**: `bg-purple-600`
---
## Implementation Complexity
### Phase 1: Core Gallery (Estimated: 8-12 hours)
- [ ] Database schema updates (Media model)
- [ ] File upload endpoint with storage
- [ ] Media listing API with filtering
- [ ] Basic grid UI with thumbnails
- [ ] Type filtering UI
### Phase 2: Media Viewing (Estimated: 6-8 hours)
- [ ] Thumbnail generation service
- [ ] Lightbox component
- [ ] Audio player component
- [ ] Photo navigation (prev/next)
- [ ] EXIF extraction service
### Phase 3: Storage Management (Estimated: 4-6 hours)
- [ ] Storage stats calculation
- [ ] Delete single media
- [ ] Bulk delete functionality
- [ ] Storage visualization UI
- [ ] Delete confirmation dialogs
### Phase 4: Advanced Features (Estimated: 8-10 hours)
- [ ] Date range filtering
- [ ] Infinite scroll pagination
- [ ] Search by date
- [ ] Object storage support (S3)
- [ ] Media encryption at rest
### Total Estimated Time: 26-36 hours
---
## Priority Recommendation
### High Priority (Phase 1 + Phase 2)
- **Target**: MVP Gallery functionality
- **Rationale**: Core value proposition - users need to see their media
- **Features**:
- Grid view of media
- Basic type filtering
- Photo lightbox
- Audio playback
- Thumbnail generation
### Medium Priority (Phase 3)
- **Target**: Storage management
- **Rationale**: Important for self-hosted users with limited storage
- **Features**:
- Storage usage display
- Delete functionality
- Bulk operations
### Low Priority (Phase 4)
- **Target**: Advanced features
- **Rationale**: Nice-to-have enhancements
- **Features**:
- Object storage
- Date range filters
- Advanced search
---
## Migration Strategy
### Backward Compatibility
- Existing `Event.mediaPath` field can be migrated to new `Media` model
- Create migration script to:
1. Create Media records from existing mediaPath values
2. Generate thumbnails for photos
3. Update Event records to reference Media
4. Deprecate (not remove) mediaPath field
### Data Migration Script
```typescript
// Pseudocode for migration
async function migrateMedia() {
const events = await prisma.event.findMany({
where: { mediaPath: { not: null } }
});
for (const event of events) {
// Create Media record
const media = await prisma.media.create({
data: {
userId: event.userId,
eventId: event.id,
type: event.type === 'voice' ? 'voice' : 'photo',
filePath: event.mediaPath,
// ... extract metadata
}
});
// Update event to link to media
await prisma.event.update({
where: { id: event.id },
data: { mediaId: media.id, mediaPath: null }
});
}
}
```
---
## Testing Considerations
1. **Unit Tests**
- Thumbnail generation
- EXIF parsing
- Storage size calculation
- Date filtering logic
2. **Integration Tests**
- Upload flow
- Media listing with filters
- Delete operations
- Event-media linking
3. **UI Tests**
- Grid responsive layout
- Lightbox navigation
- Audio player controls
- Filter interactions
---
## Performance Considerations
1. **Thumbnail Caching**
- Generate on upload, store in cache
- Serve static thumbnails directly
2. **Pagination**
- Limit initial load to 20-50 items
- Infinite scroll for more
3. **Lazy Loading**
- Use intersection observer for images
- Load thumbnails first, full-res on demand
4. **Database Indexing**
- Index on userId + type for filtering
- Index on userId + createdAt for sorting
- Index on eventId for event-media lookups
---
## Security Considerations
1. **File Validation**
- Verify MIME types server-side
- Limit file sizes (max 50MB for photos, 20MB for audio)
- Sanitize filenames
2. **Access Control**
- Users can only access their own media
- API key authentication required for all media endpoints
3. **Storage Security**
- Store outside web root
- Optional encryption for sensitive data
- Secure file deletion (overwrite before unlink)
---
## References
- [Sharp](https://sharp.pixelplumbing.com/) - Image processing library
- [EXIF Parser](https://github.com/mifi/exiftool-vendored) - EXIF data extraction
- [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) - Audio playback
- [React Player](https://github.com/cookpete/react-player) - Audio/video player React component