137 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			137 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
 | |
|         const logs = await db
 | |
|           .select({
 | |
|             loggedAt: habitLogs.loggedAt,
 | |
|           })
 | |
|           .from(habitLogs)
 | |
|           .where(eq(habitLogs.habitId, habit.id));
 | |
| 
 | |
|         // 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.reduce(
 | |
|                 (latest, log) => (log.loggedAt > latest ? log.loggedAt : latest),
 | |
|                 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 });
 | |
|   }
 | |
| }
 |