Add user authentication with login/register

- Add users and sessions tables to database schema
- Add bcryptjs for password hashing
- Create auth API routes (login, register, logout, me)
- Add AuthProvider context for client-side auth state
- Update all API routes to require authentication and filter by userId
- Create login and register pages
- Add AppShell component for authenticated layout
- Update all pages to use AppShell and show user info
- Each user now has their own private entries and tags

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gemini Agent
2026-01-24 06:18:41 +00:00
parent 5555c1e6b5
commit 1455b0acd1
27 changed files with 1039 additions and 75 deletions

View File

@@ -2,10 +2,16 @@ 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");
@@ -13,8 +19,8 @@ export async function GET(request: NextRequest) {
const since = searchParams.get("since"); // YYYY-MM-DD
try {
// Build conditions
const conditions = [];
// 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));
}
@@ -31,7 +37,7 @@ export async function GET(request: NextRequest) {
const entries = await db
.select()
.from(schema.entries)
.where(conditions.length > 0 ? and(...conditions) : undefined)
.where(and(...conditions))
.orderBy(desc(schema.entries.date), desc(schema.entries.createdAt));
// Get tags for each entry
@@ -62,6 +68,11 @@ export async function GET(request: NextRequest) {
// POST /api/entries - Create or update today's entry
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 { date, text, mood, roughDay, tagNames } = body;
@@ -72,11 +83,16 @@ export async function POST(request: NextRequest) {
const now = Date.now();
// Check if entry exists for this date
// Check if entry exists for this date for this user
const existing = await db
.select()
.from(schema.entries)
.where(eq(schema.entries.date, date))
.where(
and(
eq(schema.entries.userId, user.id),
eq(schema.entries.date, date)
)
)
.limit(1);
let entryId: string;
@@ -98,6 +114,7 @@ export async function POST(request: NextRequest) {
entryId = uuidv4();
await db.insert(schema.entries).values({
id: entryId,
userId: user.id,
date,
text,
mood: mood ?? null,
@@ -117,11 +134,16 @@ export async function POST(request: NextRequest) {
const normalizedName = name.toLowerCase().trim();
if (!normalizedName) continue;
// Find or create tag
// Find or create tag for this user
let tag = await db
.select()
.from(schema.tags)
.where(eq(schema.tags.name, normalizedName))
.where(
and(
eq(schema.tags.userId, user.id),
eq(schema.tags.name, normalizedName)
)
)
.limit(1);
let tagId: string;
@@ -129,6 +151,7 @@ export async function POST(request: NextRequest) {
tagId = uuidv4();
await db.insert(schema.tags).values({
id: tagId,
userId: user.id,
name: normalizedName,
createdAt: now,
});