diff --git a/backend/prisma/migrations/00000000000000_init.sql b/backend/prisma/migrations/00000000000000_init.sql index 39d74bc..e340725 100644 --- a/backend/prisma/migrations/00000000000000_init.sql +++ b/backend/prisma/migrations/00000000000000_init.sql @@ -19,10 +19,10 @@ CREATE TABLE IF NOT EXISTS "ApiKey" ( CONSTRAINT "ApiKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE ); -CREATE INDEX IF NOT EXISTS "ApiKey_userId_key" ON "ApiKey"("userId"); CREATE UNIQUE INDEX IF NOT EXISTS "ApiKey_keyHash_key" ON "ApiKey"("keyHash"); +CREATE INDEX IF NOT EXISTS "ApiKey_userId_key" ON "ApiKey"("userId"); -CREATE TABLE IF NOT EXISTS "Entry" ( +CREATE TABLE IF NOT EXISTS "Event" ( "id" TEXT NOT NULL PRIMARY KEY, "userId" TEXT NOT NULL, "date" TEXT NOT NULL, @@ -32,18 +32,18 @@ CREATE TABLE IF NOT EXISTS "Entry" ( "metadata" TEXT, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" DATETIME NOT NULL, - CONSTRAINT "Entry_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE + CONSTRAINT "Event_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE ); -CREATE INDEX IF NOT EXISTS "Entry_userId_date_key" ON "Entry"("userId", "date"); -CREATE INDEX IF NOT EXISTS "Entry_date_key" ON "Entry"("date"); +CREATE INDEX IF NOT EXISTS "Event_userId_date_key" ON "Event"("userId", "date"); +CREATE INDEX IF NOT EXISTS "Event_date_key" ON "Event"("date"); CREATE TABLE IF NOT EXISTS "Journal" ( "id" TEXT NOT NULL PRIMARY KEY, "userId" TEXT NOT NULL, "date" TEXT NOT NULL, "content" TEXT NOT NULL, - "entryCount" INTEGER NOT NULL, + "eventCount" INTEGER NOT NULL, "generatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "Journal_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE ); @@ -86,7 +86,6 @@ CREATE TABLE IF NOT EXISTS "Settings" ( CONSTRAINT "Settings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE ); --- Create _prisma_migrations table for Prisma migrate tracking CREATE TABLE IF NOT EXISTS "_prisma_migrations" ( "id" TEXT NOT NULL PRIMARY KEY, "checksum" TEXT NOT NULL, @@ -98,6 +97,5 @@ CREATE TABLE IF NOT EXISTS "_prisma_migrations" ( "applied_steps_count" INTEGER NOT NULL DEFAULT 0 ); --- Record this migration INSERT INTO "_prisma_migrations" ("id", "checksum", "finished_at", "migration_name", "applied_steps_count") VALUES (lower(hex(randomblob(16))), 'init', datetime('now'), '00000000000000_init', 1); diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..2a5a444 --- /dev/null +++ b/backend/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "sqlite" diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 62c7385..50eaf01 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -15,7 +15,7 @@ model User { updatedAt DateTime @updatedAt apiKeys ApiKey[] - entries Entry[] + events Event[] journals Journal[] tasks Task[] settings Settings? @@ -34,7 +34,7 @@ model ApiKey { @@index([userId]) } -model Entry { +model Event { id String @id @default(uuid()) userId String date String @@ -56,7 +56,7 @@ model Journal { userId String date String content String - entryCount Int + eventCount Int generatedAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) diff --git a/backend/src/index.ts b/backend/src/index.ts index 66e8ed6..b73ce26 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -147,7 +147,7 @@ app.get('/api/v1/days', async (c) => { const userId = await getUserId(c); if (!userId) return c.json({ data: null, error: { code: 'UNAUTHORIZED', message: 'Invalid API key' } }, 401); - const days = await prisma.entry.groupBy({ + const days = await prisma.event.groupBy({ by: ['date'], where: { userId }, _count: { id: true }, @@ -163,7 +163,7 @@ app.get('/api/v1/days', async (c) => { const result = days.map(day => ({ date: day.date, - entryCount: day._count.id, + eventCount: day._count.id, hasJournal: journalMap.has(day.date), journalGeneratedAt: journalMap.get(day.date)?.generatedAt, })); @@ -181,12 +181,12 @@ app.get('/api/v1/days/:date', async (c) => { return c.json({ data: null, error: { code: 'VALIDATION_ERROR', message: 'Invalid date format. Use YYYY-MM-DD' } }, 400); } - const [entries, journal] = await Promise.all([ - prisma.entry.findMany({ where: { userId, date }, orderBy: { createdAt: 'asc' } }), + const [events, journal] = await Promise.all([ + prisma.event.findMany({ where: { userId, date }, orderBy: { createdAt: 'asc' } }), prisma.journal.findFirst({ where: { userId, date } }), ]); - return c.json({ data: { date, entries, journal }, error: null }); + return c.json({ data: { date, events, journal }, error: null }); }); app.delete('/api/v1/days/:date', async (c) => { @@ -196,15 +196,31 @@ app.delete('/api/v1/days/:date', async (c) => { const { date } = c.req.param(); await prisma.$transaction([ - prisma.entry.deleteMany({ where: { userId, date } }), + prisma.event.deleteMany({ where: { userId, date } }), prisma.journal.deleteMany({ where: { userId, date } }), ]); return c.json({ data: { deleted: true }, error: null }); }); -// Entries routes -app.post('/api/v1/entries', async (c) => { +// Delete journal only (keeps events) +app.delete('/api/v1/journal/:date', async (c) => { + const userId = await getUserId(c); + if (!userId) return c.json({ data: null, error: { code: 'UNAUTHORIZED', message: 'Invalid API key' } }, 401); + + const { date } = c.req.param(); + + const journal = await prisma.journal.findFirst({ where: { userId, date } }); + if (!journal) { + return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Journal not found' } }, 404); + } + + await prisma.journal.delete({ where: { id: journal.id } }); + return c.json({ data: { deleted: true }, error: null }); +}); + +// Events routes +app.post('/api/v1/events', async (c) => { const userId = await getUserId(c); if (!userId) return c.json({ data: null, error: { code: 'UNAUTHORIZED', message: 'Invalid API key' } }, 401); @@ -214,45 +230,45 @@ app.post('/api/v1/entries', async (c) => { return c.json({ data: null, error: { code: 'VALIDATION_ERROR', message: 'date, type, and content are required' } }, 400); } - const validTypes = ['text', 'voice', 'photo', 'health', 'location']; + const validTypes = ['event']; if (!validTypes.includes(type)) { return c.json({ data: null, error: { code: 'VALIDATION_ERROR', message: `type must be one of: ${validTypes.join(', ')}` } }, 400); } - const entry = await prisma.entry.create({ + const event = await prisma.event.create({ data: { userId, date, type, content, metadata: metadata ? JSON.stringify(metadata) : null }, }); - return c.json({ data: entry, error: null }, 201); + return c.json({ data: event, error: null }, 201); }); -app.get('/api/v1/entries/:id', async (c) => { +app.get('/api/v1/events/:id', async (c) => { const userId = await getUserId(c); if (!userId) return c.json({ data: null, error: { code: 'UNAUTHORIZED', message: 'Invalid API key' } }, 401); const { id } = c.req.param(); - const entry = await prisma.entry.findFirst({ where: { id, userId } }); + const event = await prisma.event.findFirst({ where: { id, userId } }); - if (!entry) return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Entry not found' } }, 404); - return c.json({ data: entry, error: null }); + if (!event) return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Event not found' } }, 404); + return c.json({ data: event, error: null }); }); -app.put('/api/v1/entries/:id', async (c) => { +app.put('/api/v1/events/:id', async (c) => { const userId = await getUserId(c); if (!userId) return c.json({ data: null, error: { code: 'UNAUTHORIZED', message: 'Invalid API key' } }, 401); const { id } = c.req.param(); const { content, metadata } = await c.req.json(); - const existing = await prisma.entry.findFirst({ where: { id, userId } }); - if (!existing) return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Entry not found' } }, 404); + const existing = await prisma.event.findFirst({ where: { id, userId } }); + if (!existing) return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Event not found' } }, 404); const journal = await prisma.journal.findFirst({ where: { userId, date: existing.date } }); if (journal) { - return c.json({ data: null, error: { code: 'ENTRY_IMMUTABLE', message: 'Cannot edit entry: journal already generated. Delete the journal first.' } }, 400); + return c.json({ data: null, error: { code: 'EVENT_IMMUTABLE', message: 'Cannot edit event: journal already generated. Delete the journal first.' } }, 400); } - const entry = await prisma.entry.update({ + const event = await prisma.event.update({ where: { id }, data: { content: content ?? existing.content, @@ -260,23 +276,23 @@ app.put('/api/v1/entries/:id', async (c) => { }, }); - return c.json({ data: entry, error: null }); + return c.json({ data: event, error: null }); }); -app.delete('/api/v1/entries/:id', async (c) => { +app.delete('/api/v1/events/:id', async (c) => { const userId = await getUserId(c); if (!userId) return c.json({ data: null, error: { code: 'UNAUTHORIZED', message: 'Invalid API key' } }, 401); const { id } = c.req.param(); - const existing = await prisma.entry.findFirst({ where: { id, userId } }); - if (!existing) return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Entry not found' } }, 404); + const existing = await prisma.event.findFirst({ where: { id, userId } }); + if (!existing) return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Event not found' } }, 404); const journal = await prisma.journal.findFirst({ where: { userId, date: existing.date } }); if (journal) { - return c.json({ data: null, error: { code: 'ENTRY_IMMUTABLE', message: 'Cannot delete entry: journal already generated. Delete the journal first.' } }, 400); + return c.json({ data: null, error: { code: 'EVENT_IMMUTABLE', message: 'Cannot delete event: journal already generated. Delete the journal first.' } }, 400); } - await prisma.entry.delete({ where: { id } }); + await prisma.event.delete({ where: { id } }); return c.json({ data: { deleted: true }, error: null }); }); @@ -287,27 +303,30 @@ app.post('/api/v1/journal/generate/:date', async (c) => { const { date } = c.req.param(); - const [entries, settings] = await Promise.all([ - prisma.entry.findMany({ where: { userId, date }, orderBy: { createdAt: 'asc' } }), + const [events, settings] = await Promise.all([ + prisma.event.findMany({ where: { userId, date }, orderBy: { createdAt: 'asc' } }), prisma.settings.findUnique({ where: { userId } }), ]); - if (entries.length === 0) { - return c.json({ data: null, error: { code: 'NO_ENTRIES', message: 'No entries found for this date' } }, 400); + if (events.length === 0) { + return c.json({ data: null, error: { code: 'NO_EVENTS', message: 'No events found for this date' } }, 400); } const provider = settings?.aiProvider || 'groq'; + const providerSettings = settings?.providerSettings ? JSON.parse(settings.providerSettings) : {}; + const providerConfig = providerSettings[provider] || {}; + const apiKey = providerConfig.apiKey || settings?.aiApiKey; - if ((provider === 'openai' || provider === 'anthropic' || provider === 'groq') && !settings?.aiApiKey) { + if ((provider === 'openai' || provider === 'anthropic' || provider === 'groq') && !apiKey) { return c.json({ data: null, error: { code: 'NO_AI_CONFIG', message: 'AI not configured. Please set up your API key in settings.' } }, 400); } - // Build entries text - const entriesText = entries.map(entry => { - let text = `[${entry.type.toUpperCase()}] ${entry.createdAt.toISOString()}\n${entry.content}`; - if (entry.metadata) { + // Build events text + const eventsText = events.map(event => { + let text = `[EVENT] ${event.createdAt.toISOString()}\n${event.content}`; + if (event.metadata) { try { - const meta = JSON.parse(entry.metadata); + const meta = JSON.parse(event.metadata); if (meta.location) text += `\nLocation: ${meta.location.lat}, ${meta.location.lng}`; if (meta.duration) text += `\nDuration: ${meta.duration}s`; } catch {} @@ -342,15 +361,15 @@ app.post('/api/v1/journal/generate/:date', async (c) => { } } - // Build prompts: 1. system prompt, 2. previous journals, 3. today's entries + // Build prompts: 1. system prompt, 2. previous journals, 3. today's events const systemPrompt = settings?.journalPrompt || 'You are a thoughtful journal writer.'; - const userPrompt = `${previousJournalsText}ENTRIES FROM TODAY (${date}):\n${entriesText}\n\nWrite a thoughtful, reflective journal entry based on the entries above.`; + const userPrompt = `${previousJournalsText}EVENTS FROM TODAY (${date}):\n${eventsText}\n\nWrite a thoughtful, reflective journal entry based on the events above.`; - console.log(`[Journal Generate] Date: ${date}, Context days: ${contextDays}, Entries: ${entries.length}`); + console.log(`[Journal Generate] Date: ${date}, Context days: ${contextDays}, Events: ${events.length}`); // Create placeholder journal and task const placeholderJournal = await prisma.journal.create({ - data: { userId, date, content: 'Generating...', entryCount: entries.length }, + data: { userId, date, content: 'Generating...', eventCount: events.length }, }); const task = await prisma.task.create({ @@ -376,11 +395,14 @@ app.post('/api/v1/journal/generate/:date', async (c) => { try { console.log(`[Journal Generate] Using provider: ${provider}`); + const model = providerConfig.model || settings?.aiModel; + const baseUrl = providerConfig.baseUrl || settings?.aiBaseUrl; + const aiProvider = createAIProvider({ provider: provider as 'openai' | 'anthropic' | 'ollama' | 'lmstudio' | 'groq', - apiKey: settings?.aiApiKey || '', - model: settings?.aiModel || undefined, - baseUrl: (provider === 'ollama' || provider === 'lmstudio') ? settings?.aiBaseUrl || undefined : undefined, + apiKey: apiKey, + model: model || undefined, + baseUrl: (provider === 'ollama' || provider === 'lmstudio') ? baseUrl : undefined, }); console.log(`[Journal Generate] AI Provider created: ${aiProvider.provider}`); diff --git a/backend/src/routes/days.ts b/backend/src/routes/days.ts index c12f3fb..b991e66 100644 --- a/backend/src/routes/days.ts +++ b/backend/src/routes/days.ts @@ -7,7 +7,7 @@ daysRoutes.get('/', async (c) => { const userId = c.get('userId'); const prisma = c.get('prisma'); - const days = await prisma.entry.groupBy({ + const days = await prisma.event.groupBy({ by: ['date'], where: { userId }, _count: { id: true }, @@ -16,14 +16,14 @@ daysRoutes.get('/', async (c) => { const journals = await prisma.journal.findMany({ where: { userId }, - select: { date: true, generatedAt: true, entryCount: true }, + select: { date: true, generatedAt: true, eventCount: true }, }); const journalMap = new Map(journals.map(j => [j.date, j])); const result = days.map(day => ({ date: day.date, - entryCount: day._count.id, + eventCount: day._count.id, hasJournal: journalMap.has(day.date), journalGeneratedAt: journalMap.get(day.date)?.generatedAt, })); @@ -41,8 +41,8 @@ daysRoutes.get('/:date', async (c) => { return c.json({ data: null, error: { code: 'VALIDATION_ERROR', message: 'Invalid date format. Use YYYY-MM-DD' } }, 400); } - const [entries, journal] = await Promise.all([ - prisma.entry.findMany({ + const [events, journal] = await Promise.all([ + prisma.event.findMany({ where: { userId, date }, orderBy: { createdAt: 'asc' }, }), @@ -51,7 +51,7 @@ daysRoutes.get('/:date', async (c) => { }), ]); - return c.json({ data: { date, entries, journal }, error: null }); + return c.json({ data: { date, events, journal }, error: null }); }); daysRoutes.delete('/:date', async (c) => { @@ -60,9 +60,29 @@ daysRoutes.delete('/:date', async (c) => { const prisma = c.get('prisma'); await prisma.$transaction([ - prisma.entry.deleteMany({ where: { userId, date } }), + prisma.event.deleteMany({ where: { userId, date } }), prisma.journal.deleteMany({ where: { userId, date } }), ]); return c.json({ data: { deleted: true }, error: null }); }); + +daysRoutes.delete('/:date/journal', async (c) => { + const userId = c.get('userId'); + const { date } = c.req.param(); + const prisma = c.get('prisma'); + + const journal = await prisma.journal.findUnique({ + where: { userId_date: { userId, date } }, + }); + + if (!journal) { + return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Journal not found' } }, 404); + } + + await prisma.journal.delete({ + where: { id: journal.id }, + }); + + return c.json({ data: { deleted: true }, error: null }); +}); diff --git a/backend/src/routes/entries.ts b/backend/src/routes/events.ts similarity index 68% rename from backend/src/routes/entries.ts rename to backend/src/routes/events.ts index 7ee2b35..8eafd8e 100644 --- a/backend/src/routes/entries.ts +++ b/backend/src/routes/events.ts @@ -1,9 +1,9 @@ import { Hono } from 'hono'; import { HonoEnv } from '../lib/types'; -export const entriesRoutes = new Hono(); +export const eventsRoutes = new Hono(); -entriesRoutes.post('/', async (c) => { +eventsRoutes.post('/', async (c) => { const userId = c.get('userId'); const prisma = c.get('prisma'); const mediaDir = c.env.MEDIA_DIR || './data/media'; @@ -15,12 +15,12 @@ entriesRoutes.post('/', async (c) => { return c.json({ data: null, error: { code: 'VALIDATION_ERROR', message: 'date, type, and content are required' } }, 400); } - const validTypes = ['text', 'voice', 'photo', 'health', 'location']; + const validTypes = ['event', 'text', 'photo', 'voice', 'health']; if (!validTypes.includes(type)) { return c.json({ data: null, error: { code: 'VALIDATION_ERROR', message: `type must be one of: ${validTypes.join(', ')}` } }, 400); } - const entry = await prisma.entry.create({ + const event = await prisma.event.create({ data: { userId, date, @@ -30,26 +30,26 @@ entriesRoutes.post('/', async (c) => { }, }); - return c.json({ data: entry, error: null }, 201); + return c.json({ data: event, error: null }, 201); }); -entriesRoutes.get('/:id', async (c) => { +eventsRoutes.get('/:id', async (c) => { const userId = c.get('userId'); const { id } = c.req.param(); const prisma = c.get('prisma'); - const entry = await prisma.entry.findFirst({ + const event = await prisma.event.findFirst({ where: { id, userId }, }); - if (!entry) { - return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Entry not found' } }, 404); + if (!event) { + return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Event not found' } }, 404); } - return c.json({ data: entry, error: null }); + return c.json({ data: event, error: null }); }); -entriesRoutes.put('/:id', async (c) => { +eventsRoutes.put('/:id', async (c) => { const userId = c.get('userId'); const { id } = c.req.param(); const prisma = c.get('prisma'); @@ -57,20 +57,20 @@ entriesRoutes.put('/:id', async (c) => { const body = await c.req.json(); const { content, metadata } = body; - const existing = await prisma.entry.findFirst({ + const existing = await prisma.event.findFirst({ where: { id, userId }, }); if (!existing) { - return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Entry not found' } }, 404); + return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Event not found' } }, 404); } const journal = await prisma.journal.findFirst({ where: { userId, date: existing.date } }); if (journal) { - return c.json({ data: null, error: { code: 'ENTRY_IMMUTABLE', message: 'Cannot edit entry: journal already generated. Delete the journal first.' } }, 400); + return c.json({ data: null, error: { code: 'EVENT_IMMUTABLE', message: 'Cannot edit event: diary already generated. Delete the diary first.' } }, 400); } - const entry = await prisma.entry.update({ + const event = await prisma.event.update({ where: { id }, data: { content: content ?? existing.content, @@ -78,44 +78,44 @@ entriesRoutes.put('/:id', async (c) => { }, }); - return c.json({ data: entry, error: null }); + return c.json({ data: event, error: null }); }); -entriesRoutes.delete('/:id', async (c) => { +eventsRoutes.delete('/:id', async (c) => { const userId = c.get('userId'); const { id } = c.req.param(); const prisma = c.get('prisma'); - const existing = await prisma.entry.findFirst({ + const existing = await prisma.event.findFirst({ where: { id, userId }, }); if (!existing) { - return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Entry not found' } }, 404); + return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Event not found' } }, 404); } const journal = await prisma.journal.findFirst({ where: { userId, date: existing.date } }); if (journal) { - return c.json({ data: null, error: { code: 'ENTRY_IMMUTABLE', message: 'Cannot delete entry: journal already generated. Delete the journal first.' } }, 400); + return c.json({ data: null, error: { code: 'EVENT_IMMUTABLE', message: 'Cannot delete event: diary already generated. Delete the diary first.' } }, 400); } - await prisma.entry.delete({ where: { id } }); + await prisma.event.delete({ where: { id } }); return c.json({ data: { deleted: true }, error: null }); }); -entriesRoutes.post('/:id/photo', async (c) => { +eventsRoutes.post('/:id/photo', async (c) => { const userId = c.get('userId'); const { id } = c.req.param(); const prisma = c.get('prisma'); const mediaDir = c.env.MEDIA_DIR || './data/media'; - const entry = await prisma.entry.findFirst({ + const event = await prisma.event.findFirst({ where: { id, userId }, }); - if (!entry) { - return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Entry not found' } }, 404); + if (!event) { + return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Event not found' } }, 400); } const body = await c.req.parseBody(); @@ -127,12 +127,12 @@ entriesRoutes.post('/:id/photo', async (c) => { const ext = file.name.split('.').pop() || 'jpg'; const fileName = `${id}.${ext}`; - const userMediaDir = `${mediaDir}/${userId}/${entry.date}`; + const userMediaDir = `${mediaDir}/${userId}/${event.date}`; const filePath = `${userMediaDir}/${fileName}`; await Bun.write(filePath, file); - await prisma.entry.update({ + await prisma.event.update({ where: { id }, data: { mediaPath: filePath }, }); @@ -140,18 +140,18 @@ entriesRoutes.post('/:id/photo', async (c) => { return c.json({ data: { mediaPath: filePath }, error: null }, 201); }); -entriesRoutes.post('/:id/voice', async (c) => { +eventsRoutes.post('/:id/voice', async (c) => { const userId = c.get('userId'); const { id } = c.req.param(); const prisma = c.get('prisma'); const mediaDir = c.env.MEDIA_DIR || './data/media'; - const entry = await prisma.entry.findFirst({ + const event = await prisma.event.findFirst({ where: { id, userId }, }); - if (!entry) { - return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Entry not found' } }, 404); + if (!event) { + return c.json({ data: null, error: { code: 'NOT_FOUND', message: 'Event not found' } }, 400); } const body = await c.req.parseBody(); @@ -162,12 +162,12 @@ entriesRoutes.post('/:id/voice', async (c) => { } const fileName = `${id}.webm`; - const userMediaDir = `${mediaDir}/${userId}/${entry.date}`; + const userMediaDir = `${mediaDir}/${userId}/${event.date}`; const filePath = `${userMediaDir}/${fileName}`; await Bun.write(filePath, file); - await prisma.entry.update({ + await prisma.event.update({ where: { id }, data: { mediaPath: filePath }, }); diff --git a/backend/src/routes/journal.ts b/backend/src/routes/journal.ts index 2b462ea..87ddac2 100644 --- a/backend/src/routes/journal.ts +++ b/backend/src/routes/journal.ts @@ -9,8 +9,8 @@ journalRoutes.post('/generate/:date', async (c) => { const { date } = c.req.param(); const prisma = c.get('prisma'); - const [entries, settings] = await Promise.all([ - prisma.entry.findMany({ + const [events, settings] = await Promise.all([ + prisma.event.findMany({ where: { userId, date }, orderBy: { createdAt: 'asc' }, }), @@ -19,8 +19,8 @@ journalRoutes.post('/generate/:date', async (c) => { }), ]); - if (entries.length === 0) { - return c.json({ data: null, error: { code: 'NO_ENTRIES', message: 'No entries found for this date' } }, 400); + if (events.length === 0) { + return c.json({ data: null, error: { code: 'NO_EVENTS', message: 'No events found for this date' } }, 400); } if (!settings?.aiApiKey) { @@ -34,11 +34,11 @@ journalRoutes.post('/generate/:date', async (c) => { baseUrl: settings.aiBaseUrl, }); - const entriesText = entries.map(entry => { - let text = `[${entry.type.toUpperCase()}] ${entry.createdAt.toISOString()}\n${entry.content}`; - if (entry.metadata) { + const eventsText = events.map(event => { + let text = `[${event.type.toUpperCase()}] ${event.createdAt.toISOString()}\n${event.content}`; + if (event.metadata) { try { - const meta = JSON.parse(entry.metadata); + const meta = JSON.parse(event.metadata); if (meta.location) text += `\nLocation: ${meta.location.lat}, ${meta.location.lng}`; if (meta.duration) text += `\nDuration: ${meta.duration}s`; } catch {} @@ -46,17 +46,17 @@ journalRoutes.post('/generate/:date', async (c) => { return text; }).join('\n\n'); - const prompt = `The following entries were captured throughout the day (${date}). Write a thoughtful, reflective journal entry that: + const prompt = `The following events were captured throughout the day (${date}). Write a thoughtful, reflective diary page that: 1. Summarizes the key moments and activities 2. Reflects on any patterns, feelings, or insights 3. Ends with a forward-looking thought Use a warm, personal tone. The journal should flow naturally as prose. -ENTRIES: -${entriesText} +EVENTS: +${eventsText} -JOURNAL:`; +DIARY PAGE:`; try { const content = await provider.generate(prompt, settings.journalPrompt); @@ -67,11 +67,11 @@ JOURNAL:`; userId, date, content, - entryCount: entries.length, + eventCount: events.length, }, update: { content, - entryCount: entries.length, + eventCount: events.length, generatedAt: new Date(), }, }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0cdde6b..5798b00 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -52,6 +52,15 @@ function Navbar() { ); } +function Footer() { + const { resolvedTheme } = useTheme(); + return ( +
+

DearDiary.io — Self-hosted AI-powered journaling · GitHub · deardiary.io · MIT License · © 2024 Konrad Lother

+
+ ); +} + function App() { const [isAuthenticated, setIsAuthenticated] = useState(null); const { resolvedTheme } = useTheme(); @@ -97,6 +106,7 @@ function App() { } /> } /> +