Files
Felix Schulze 65f1fcb7bb Adds habit edit, archive, and undo log features
Enables users to update or archive habits and to undo the latest habit log.
Adds PATCH/DELETE API endpoints for habit edit and soft deletion.
Introduces UI dialogs and controls for editing and archiving habits,
as well as for undoing the most recent log entry directly from the dashboard.
Improves log statistics handling by ordering and simplifies last log detection.
2025-11-24 22:12:25 +01:00

107 lines
3.0 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { db, habits, users, habitLogs } from '@/lib/db';
import { getTokenCookie } from '@/lib/auth/cookies';
import { eq, and, desc } from 'drizzle-orm';
async function getUserFromToken() {
const token = await getTokenCookie();
if (!token) return null;
const userRows = await db.select().from(users).where(eq(users.token, token));
return userRows.length > 0 ? userRows[0] : null;
}
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
try {
const { id } = await params;
const habitId = parseInt(id);
if (isNaN(habitId)) {
return NextResponse.json({ error: 'Invalid habit ID' }, { status: 400 });
}
const user = await getUserFromToken();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Verify habit belongs to user
const habitRows = await db
.select()
.from(habits)
.where(and(eq(habits.id, habitId), eq(habits.userId, user.id)));
if (habitRows.length === 0) {
return NextResponse.json({ error: 'Habit not found' }, { status: 404 });
}
const body = (await request.json()) as { note?: string };
const { note } = body;
// Create log entry
const logRows = await db
.insert(habitLogs)
.values({
habitId,
note,
})
.returning();
if (logRows.length === 0) {
throw new Error('Failed to create log entry');
}
const log = logRows[0];
return NextResponse.json({ log });
} catch (error) {
console.error('Log habit error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
export async function DELETE(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
try {
const { id } = await params;
const habitId = parseInt(id);
if (isNaN(habitId)) {
return NextResponse.json({ error: 'Invalid habit ID' }, { status: 400 });
}
const user = await getUserFromToken();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Verify habit belongs to user
const habitRows = await db
.select()
.from(habits)
.where(and(eq(habits.id, habitId), eq(habits.userId, user.id)));
if (habitRows.length === 0) {
return NextResponse.json({ error: 'Habit not found' }, { status: 404 });
}
// Find latest log
const latestLog = await db
.select()
.from(habitLogs)
.where(eq(habitLogs.habitId, habitId))
.orderBy(desc(habitLogs.loggedAt))
.limit(1);
if (latestLog.length === 0) {
return NextResponse.json({ error: 'No logs to undo' }, { status: 404 });
}
// Delete latest log
await db.delete(habitLogs).where(eq(habitLogs.id, latestLog[0].id));
return NextResponse.json({ success: true });
} catch (error) {
console.error('Undo log error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}