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.
133 lines
3.9 KiB
TypeScript
133 lines
3.9 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 GET() {
|
|
try {
|
|
const user = await getUserFromToken();
|
|
if (!user) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
// Get current timestamp for date calculations
|
|
const now = new Date();
|
|
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
|
|
// First get all habits
|
|
const userHabitsBase = await db
|
|
.select()
|
|
.from(habits)
|
|
.where(and(eq(habits.userId, user.id), eq(habits.isArchived, false)))
|
|
.orderBy(desc(habits.createdAt));
|
|
|
|
// Then get aggregated log data for each habit
|
|
const habitsWithStats = await Promise.all(
|
|
userHabitsBase.map(async (habit) => {
|
|
// Get all logs for this habit, ordered by date desc
|
|
const logs = await db
|
|
.select({
|
|
id: habitLogs.id,
|
|
loggedAt: habitLogs.loggedAt,
|
|
})
|
|
.from(habitLogs)
|
|
.where(eq(habitLogs.habitId, habit.id))
|
|
.orderBy(desc(habitLogs.loggedAt));
|
|
|
|
// Calculate statistics
|
|
const totalLogs = logs.length;
|
|
const logsLastWeek = logs.filter((log) => log.loggedAt >= sevenDaysAgo).length;
|
|
const logsLastMonth = logs.filter((log) => log.loggedAt >= thirtyDaysAgo).length;
|
|
const lastLoggedAt = logs.length > 0 ? logs[0].loggedAt : null;
|
|
|
|
return {
|
|
id: habit.id,
|
|
name: habit.name,
|
|
type: habit.type,
|
|
targetFrequency: habit.targetFrequency,
|
|
color: habit.color,
|
|
icon: habit.icon,
|
|
createdAt: habit.createdAt,
|
|
lastLoggedAt,
|
|
totalLogs,
|
|
logsLastWeek,
|
|
logsLastMonth,
|
|
};
|
|
}),
|
|
);
|
|
|
|
return NextResponse.json({ habits: habitsWithStats });
|
|
} catch (error) {
|
|
console.error('Get habits error:', error);
|
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const user = await getUserFromToken();
|
|
if (!user) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
const body = (await request.json()) as {
|
|
name: string;
|
|
type: string;
|
|
targetFrequency?: { value: number; period: 'day' | 'week' | 'month' };
|
|
color?: string;
|
|
icon?: string;
|
|
};
|
|
const { name, type, targetFrequency, color, icon } = body;
|
|
|
|
if (!name || !type) {
|
|
return NextResponse.json(
|
|
{
|
|
error: 'Name and type are required',
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
// Validate type is one of the allowed enum values
|
|
if (!['positive', 'neutral', 'negative'].includes(type)) {
|
|
return NextResponse.json(
|
|
{
|
|
error: 'Type must be one of: positive, neutral, negative',
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const newHabitRows = await db
|
|
.insert(habits)
|
|
.values({
|
|
userId: user.id,
|
|
name,
|
|
type: type as 'positive' | 'neutral' | 'negative',
|
|
targetFrequency,
|
|
color,
|
|
icon,
|
|
})
|
|
.returning();
|
|
|
|
if (newHabitRows.length === 0) {
|
|
throw new Error('Failed to create habit');
|
|
}
|
|
|
|
const newHabit = newHabitRows[0];
|
|
return NextResponse.json({ habit: newHabit });
|
|
} catch (error) {
|
|
console.error('Create habit error:', error);
|
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
}
|
|
}
|