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
This commit is contained in:
lotherk
2026-03-27 02:27:55 +00:00
parent deaf496a7d
commit 0bdd71a4ed
67 changed files with 15201 additions and 355 deletions

537
todo/gallery.md Normal file
View File

@@ -0,0 +1,537 @@
# 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