7.0 KiB
7.0 KiB
DearDiary - AI-Powered Daily Journal
Self-hosted journaling app where users capture events throughout the day and AI generates diary pages. Events become immutable once a diary is generated (locked).
Tech Stack
- Backend: Bun + Hono + Prisma (SQLite)
- Frontend: React + Vite + TypeScript + Tailwind CSS
- Database: SQLite (file-based)
- Deployment: Docker
Project Structure
/backend
/prisma
schema.prisma # Database schema (models: User, Event, Journal, Task, Settings)
/src
index.ts # Main API routes, journal generation logic
/services/ai # AI provider implementations (Groq, OpenAI, Anthropic, Ollama, LMStudio)
/__tests__/ # API unit tests (Bun test)
/frontend
/src
/pages # Page components (Dashboard, Home, Journal, Diary, Settings, Day, Tasks, Calendar)
/components # Reusable components (QuickAddWidget, SearchModal, DateNavigator, EntryList, etc.)
/lib
api.ts # API client with typed methods
geolocation.ts # Browser geolocation with reverse geocoding
ThemeContext.tsx # Dark/light theme
/www
index.html # Product landing page
/css # Stylesheets
/js # JavaScript
/docs # HTML documentation pages
Key Concepts
Event vs Journal
- Event: User input during the day (immutable after journal is generated)
- Journal: AI-generated diary page from events (can regenerate, title + content)
Terminology
- "Diary Page" not "Journal"
- "Event" not "Entry"
- "Generate" not "Create"
- "Rewrite" not "Regenerate"
- "Today" is the event stream page (
/today)
Routes
/- Dashboard (recent diary pages with excerpts)/today- Today's event stream (main capture page)/diary- Paginated diary reader (10/50/100 per page)/journal/:date- View/edit diary page with generation tasks/day/:date- View day's events with DateNavigator/settings- Configuration/calendar- Month calendar view
Database Schema
Journal Model (key fields)
id, userId, date, title, content, eventCount, generatedAt
Task Model
Stores generation attempts with full request/response JSON for debugging.
id, userId, journalId, type, status, provider, model, prompt, request, response, error, title, createdAt, completedAt
Event Model (key fields)
id, userId, date, type, content, mediaPath, metadata, latitude, longitude, placeName, createdAt
- Location is captured automatically from browser geolocation when creating events
- Reverse geocoding via OpenStreetMap Nominatim API provides place names
API Design
All endpoints return: { data: T | null, error: { code, message } | null }
Authentication
- API key in
Authorization: Bearer <key>header - Keys stored as SHA-256 hashes
Key Endpoints
POST /api/v1/journal/generate/:date # Generate diary (with optional instructions)
GET /api/v1/journal/:date # Get diary page
DELETE /api/v1/journal/:date # Delete to unlock events
GET /api/v1/journal/:date/tasks # Generation tasks (includes title per task)
GET /api/v1/journals # List journals with pagination (?page=1&limit=10)
GET /api/v1/days # List days with journal info (includes excerpt)
POST /api/v1/events # Create event
GET /api/v1/export # Export all user data (JSON)
POST /api/v1/import # Import data (with version checking)
Export/Import
Export Format
Exports include:
version: DearDiary version string (e.g., "0.1.0")exportedAt: ISO timestamp of exportsettings: User settings including AI provider configurationevents: All user events (includes latitude, longitude, placeName)journals: All generated diary pagestasks: All generation tasks
Version Compatibility
- Minimum supported import version: 0.0.3
- Import validates version compatibility
- Warns if importing older/newer version
- Older exports may fail or lose data
Import Behavior
- Duplicates are skipped (based on date + content + timestamp for events)
- Journals matched by date
- Tasks linked to journals by date
- Settings are overwritten
AI Integration
Providers
- Groq (default, uses llama-3.3-70b-versatile)
- OpenAI
- Anthropic
- Ollama (local)
- LM Studio (local)
Prompt System
- Default system prompt is defined in
backend/src/index.ts(hardcoded, not user-configurable) - User can add custom instructions via
settings.journalPromptfield (labeled "Prompt" in UI) - Custom instructions are prepended to the default prompt when set
- Default prompt includes anti-hallucination rules and structure guidelines
JSON Mode
AI is configured to return JSON with response_format: { type: "json_object" } where supported. Journal generation prompts instruct AI to return:
{"title": "Short title", "content": "Diary text..."}
Provider Settings Storage
Settings stored as providerSettings: { "groq": { apiKey, model, baseUrl }, ... } with aiProvider determining which is active.
Coding Guidelines
TypeScript
- Use explicit interfaces for API responses
- Avoid
anytypes - Use optional chaining and nullish coalescing
React Components
- Functional components with hooks
- Props interfaces defined at top of file
- Use
useStatefor local state,useEffectfor data loading
Tailwind CSS
- Dark theme by default (slate color palette)
- Use
text-slate-400for muted text - Use
purple-*for primary actions - Use
slate-900for cards/containers
API Response Handling
const res = await api.someMethod();
if (res.error) {
// handle error
} else if (res.data) {
// use res.data
}
Error Handling
- Backend returns
{ code, message }errors - Frontend displays errors inline or as toast notifications
- Generation errors shown in red banner
Common Tasks
Adding a new AI provider
- Create
/backend/src/services/ai/<provider>.ts - Implement
AIProviderinterface withgenerate(prompt, systemPrompt, options?) - Add to
provider.tscreateAIProvider()switch - Add
jsonModeparsing if supported
Database migrations
cd backend
bunx prisma migrate dev --name migration_name
Docker rebuild
docker compose build && docker compose up -d
Running Tests
cd backend
bun run test:server
Tests require the server running. The test script starts the server, runs tests, then stops it.
Version History
- 0.1.0: Automatic geolocation capture, Starlight documentation site
- 0.0.6: Automatic geolocation capture on event creation, reverse geocoding to place names
- 0.0.5: Export/Import feature with version checking
- 0.0.4: /diary page with pagination (10/50/100), Task.title field, dashboard excerpts
- 0.0.3: AI returns JSON with title + content, UI shows titles
- 0.0.2: Dashboard, Quick Add widget (Ctrl+J), rewrite modal
- 0.0.1: Initial release with Entry->Event terminology fix