diff --git a/CHANGELOG.md b/CHANGELOG.md index 9257fae..46f05c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Changed +- **Strict anti-hallucination default prompt**: New users get a grounded, factual prompt that ONLY summarizes what's recorded - no invention, no embellishment + +## [0.2.0] - 2026-03-26 + ### Added - **Task System**: AI journal generation now creates tasks that track: - Request sent to AI provider (full prompt + config) @@ -17,6 +22,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `GET /api/v1/tasks/:id` endpoint - **Theme System**: Light/Dark/System theme toggle - **Branding**: "DearDiary.io" logo in navbar +- **Default user**: Auto-created via `DEFAULT_USER_EMAIL` and `DEFAULT_USER_PASSWORD` env vars +- `/login` route (was `/auth`) ### Changed - **Renamed project from "TotalRecall" to "DearDiary"** diff --git a/Dockerfile b/Dockerfile index 06b039a..c3609a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,4 +43,6 @@ COPY nginx.conf /etc/nginx/http.d/default.conf RUN mkdir -p /data /run /app/nginx_tmp /var/lib/nginx/logs && chmod 777 /var/lib/nginx/logs /var/lib/nginx/tmp && chown -R bun:nodejs /data /app # Start everything as root -CMD sh -c "nginx -g 'daemon off;' & bunx prisma db push --accept-data-loss & bun ./dist/index.js" +COPY start.sh /start.sh +RUN chmod +x /start.sh +CMD ["/start.sh"] diff --git a/backend/.env.example b/backend/.env.example index d1b1a1d..0078f82 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,5 +1,5 @@ # Database connection (SQLite, PostgreSQL, or MySQL) -DATABASE_URL="file:./data/totalrecall.db" +DATABASE_URL="file:./data/deardiary.db" # Media storage directory MEDIA_DIR="./data/media" @@ -13,8 +13,15 @@ PORT="3000" # CORS origin (use specific domain in production) CORS_ORIGIN="*" +# Default user (auto-created on startup if doesn't exist) +DEFAULT_USER_EMAIL="admin@localhost" +DEFAULT_USER_PASSWORD="changeme123" + +# Default journal prompt (strict anti-hallucination) +# JOURNAL_PROMPT="You are a factual diary summarizer. Your ONLY job is to summarize the entries provided to you - nothing more.\n\nCRITICAL RULES:\n1. ONLY use information explicitly stated in the entries below\n2. NEVER invent, assume, or hallucinate any detail not in the entries\n3. NEVER add activities, emotions, weather, or context not directly mentioned\n4. If something is unclear in the entries, simply state what IS clear\n5. Keep the summary grounded and factual - no embellishment\n6. Do not write in an overly creative or story-telling style\n7. Only reference what the user explicitly recorded" + # Example PostgreSQL connection: -# DATABASE_URL="postgresql://postgres:password@db:5432/totalrecall" +# DATABASE_URL="postgresql://postgres:password@db:5432/deardiary" # Example MySQL connection: -# DATABASE_URL="mysql://root:password@localhost:3306/totalrecall" +# DATABASE_URL="mysql://root:password@localhost:3306/deardiary" diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 27f17d6..c86f037 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -94,7 +94,7 @@ model Settings { aiApiKey String? aiModel String @default("gpt-4") aiBaseUrl String? - journalPrompt String @default("You are a thoughtful journal writer. Based on the entries provided, write a reflective journal entry for this day in a warm, personal tone.") + journalPrompt String @default("You are a factual diary summarizer. Your ONLY job is to summarize the entries provided to you - nothing more.\n\nCRITICAL RULES:\n1. ONLY use information explicitly stated in the entries below\n2. NEVER invent, assume, or hallucinate any detail not in the entries\n3. NEVER add activities, emotions, weather, or context not directly mentioned\n4. If something is unclear in the entries, simply state what IS clear\n5. Keep the summary grounded and factual - no embellishment\n6. Do not write in an overly creative or story-telling style\n7. Only reference what the user explicitly recorded\n\nStructure:\n- Start with what was recorded (meetings, tasks, activities)\n- Note any explicit feelings or observations mentioned\n- Keep it concise and factual\n- If there are gaps in the day, acknowledge only what was recorded") language String @default("en") user User @relation(fields: [userId], references: [id], onDelete: Cascade) diff --git a/backend/src/index.ts b/backend/src/index.ts index 0155745..d43478d 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -579,8 +579,61 @@ app.onError((err, c) => { return c.json({ data: null, error: { code: 'INTERNAL_ERROR', message: 'Internal server error' } }, 500); }); +async function createDefaultUser() { + const defaultEmail = envVars.DEFAULT_USER_EMAIL; + const defaultPassword = envVars.DEFAULT_USER_PASSWORD; + + if (!defaultEmail || !defaultPassword) { + console.log('No default user configured (set DEFAULT_USER_EMAIL and DEFAULT_USER_PASSWORD)'); + return; + } + + try { + const existing = await prisma.user.findUnique({ where: { email: defaultEmail } }); + if (existing) { + console.log(`Default user '${defaultEmail}' already exists`); + + const hasKey = await prisma.apiKey.findFirst({ where: { userId: existing.id } }); + if (!hasKey) { + const apiKey = randomBytes(32).toString('hex'); + const keyHash = createHash('sha256').update(apiKey).digest('hex'); + await prisma.apiKey.create({ + data: { userId: existing.id, keyHash, name: 'Default' } + }); + console.log(`Created API key for default user`); + console.log(`API Key: ${apiKey}`); + } + return; + } + + const passwordHash = await bcrypt.hash(defaultPassword, 12); + const user = await prisma.user.create({ + data: { + email: defaultEmail, + passwordHash, + settings: { create: {} } + } + }); + + const apiKey = randomBytes(32).toString('hex'); + const keyHash = createHash('sha256').update(apiKey).digest('hex'); + await prisma.apiKey.create({ + data: { userId: user.id, keyHash, name: 'Default' } + }); + + console.log(`Created default user: ${defaultEmail}`); + console.log(`API Key: ${apiKey}`); + } catch (err) { + console.error('Failed to create default user:', err); + } +} + const port = parseInt(envVars.PORT || '3000', 10); -console.log(`Starting TotalRecall API on port ${port}`); +console.log(`Starting DearDiary API on port ${port}`); + +createDefaultUser().then(() => { + console.log('Server ready'); +}); export default { port, diff --git a/docker-compose.yml b/docker-compose.yml index dab1c65..9e830fe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,8 +12,10 @@ services: - JWT_SECRET=${JWT_SECRET:-change-me-in-production} - PORT=3000 - CORS_ORIGIN=${CORS_ORIGIN:-*} + - DEFAULT_USER_EMAIL=${DEFAULT_USER_EMAIL:-} + - DEFAULT_USER_PASSWORD=${DEFAULT_USER_PASSWORD:-} volumes: - - ./data:/data + - deardiary_data:/data restart: unless-stopped extra_hosts: - "host.docker.internal:host-gateway" @@ -22,3 +24,6 @@ services: interval: 30s timeout: 10s retries: 3 + +volumes: + deardiary_data: diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5b5cfd1..46f4582 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import History from './pages/History'; import Day from './pages/Day'; import Journal from './pages/Journal'; import Settings from './pages/Settings'; +import { useTheme } from './lib/ThemeContext'; function PrivateRoute({ children }: { children: React.ReactNode }) { const [isAuthenticated, setIsAuthenticated] = useState(null); @@ -25,29 +26,43 @@ function PrivateRoute({ children }: { children: React.ReactNode }) { } if (!isAuthenticated) { - return ; + return ; } return <>{children}; } +function Navbar() { + return ( + + ); +} + function App() { - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [loading, setLoading] = useState(true); + const [isAuthenticated, setIsAuthenticated] = useState(null); + const { resolvedTheme } = useTheme(); useEffect(() => { const key = api.getApiKey(); setIsAuthenticated(!!key); - setLoading(false); }, []); - const handleAuth = () => { - setIsAuthenticated(true); - }; - - if (loading) { + if (isAuthenticated === null) { return ( -
+
Loading...
); @@ -55,26 +70,11 @@ function App() { return ( -
- {isAuthenticated && ( - - )} +
+ {isAuthenticated ? : null} - : + : setIsAuthenticated(true)} /> } /> diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index db65abe..f91e5dc 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -33,7 +33,7 @@ export default function SettingsPage() { const handleLogout = () => { api.clearApiKey(); - window.location.href = '/auth'; + window.location.href = '/login'; }; if (loading) { diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..d24d506 --- /dev/null +++ b/start.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +echo "Running database migrations..." +bunx prisma db push --accept-data-loss + +echo "Starting server..." +nginx -g 'daemon off;' & +exec bun ./dist/index.js