import { NextRequest, NextResponse } from "next/server"; import { db, schema } from "@/lib/db"; import { eq, desc, and, inArray, gte } from "drizzle-orm"; import { v4 as uuidv4 } from "uuid"; import { getSession } from "@/lib/auth"; import type { CreateEntryRequest, EntryWithTags } from "@/lib/types"; // GET /api/entries - List entries with optional filters export async function GET(request: NextRequest) { const user = await getSession(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const { searchParams } = new URL(request.url); const moods = searchParams.get("moods")?.split(",").map(Number).filter(Boolean); const tagId = searchParams.get("tagId"); const roughDay = searchParams.get("roughDay"); const since = searchParams.get("since"); // YYYY-MM-DD try { // Build conditions - always filter by userId const conditions = [eq(schema.entries.userId, user.id)]; if (moods && moods.length > 0) { conditions.push(inArray(schema.entries.mood, moods)); } if (roughDay === "true") { conditions.push(eq(schema.entries.roughDay, 1)); } else if (roughDay === "false") { conditions.push(eq(schema.entries.roughDay, 0)); } if (since) { conditions.push(gte(schema.entries.date, since)); } // Get entries const entries = await db .select() .from(schema.entries) .where(and(...conditions)) .orderBy(desc(schema.entries.date), desc(schema.entries.createdAt)); // Get tags for each entry const entriesWithTags: EntryWithTags[] = []; for (const entry of entries) { const entryTagRows = await db .select({ tag: schema.tags }) .from(schema.entryTags) .innerJoin(schema.tags, eq(schema.entryTags.tagId, schema.tags.id)) .where(eq(schema.entryTags.entryId, entry.id)); const tags = entryTagRows.map((row) => row.tag); // Filter by tagId if specified if (tagId && !tags.some((t) => t.id === tagId)) { continue; } entriesWithTags.push({ ...entry, tags }); } return NextResponse.json(entriesWithTags); } catch (error) { console.error("Failed to fetch entries:", error); return NextResponse.json({ error: "Failed to fetch entries" }, { status: 500 }); } } // POST /api/entries - Create a new entry or update existing by id export async function POST(request: NextRequest) { const user = await getSession(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } try { const body: CreateEntryRequest = await request.json(); const { id: existingId, date, text, mood, roughDay, tagNames } = body; if (!date || !text) { return NextResponse.json({ error: "Date and text are required" }, { status: 400 }); } const now = Date.now(); let entryId: string; if (existingId) { // Update existing entry - verify ownership const existing = await db .select() .from(schema.entries) .where( and( eq(schema.entries.id, existingId), eq(schema.entries.userId, user.id) ) ) .limit(1); if (existing.length === 0) { return NextResponse.json({ error: "Entry not found" }, { status: 404 }); } entryId = existingId; await db .update(schema.entries) .set({ text, mood: mood ?? null, roughDay: roughDay ? 1 : 0, updatedAt: now, }) .where(eq(schema.entries.id, entryId)); } else { // Create new entry entryId = uuidv4(); await db.insert(schema.entries).values({ id: entryId, userId: user.id, date, text, mood: mood ?? null, roughDay: roughDay ? 1 : 0, createdAt: now, updatedAt: now, }); } // Update tags // Remove existing tag associations await db.delete(schema.entryTags).where(eq(schema.entryTags.entryId, entryId)); // Add new tags if (tagNames && tagNames.length > 0) { for (const name of tagNames) { const normalizedName = name.toLowerCase().trim(); if (!normalizedName) continue; // Find or create tag for this user let tag = await db .select() .from(schema.tags) .where( and( eq(schema.tags.userId, user.id), eq(schema.tags.name, normalizedName) ) ) .limit(1); let tagId: string; if (tag.length === 0) { tagId = uuidv4(); await db.insert(schema.tags).values({ id: tagId, userId: user.id, name: normalizedName, createdAt: now, }); } else { tagId = tag[0].id; } // Create association await db .insert(schema.entryTags) .values({ entryId, tagId }) .onConflictDoNothing(); } } // Fetch and return the updated entry with tags const entry = await db .select() .from(schema.entries) .where(eq(schema.entries.id, entryId)) .limit(1); const entryTagRows = await db .select({ tag: schema.tags }) .from(schema.entryTags) .innerJoin(schema.tags, eq(schema.entryTags.tagId, schema.tags.id)) .where(eq(schema.entryTags.entryId, entryId)); const entryWithTags: EntryWithTags = { ...entry[0], tags: entryTagRows.map((row) => row.tag), }; return NextResponse.json(entryWithTags); } catch (error) { console.error("Failed to create/update entry:", error); return NextResponse.json({ error: "Failed to save entry" }, { status: 500 }); } }