add habit and habit completion API functions\

\
- Add comprehensive CRUD operations for habits and completions (create, update, delete, list)
- Add completion management with upsert, update, and reset functionality
- Include date-based queries and filtering for completions
This commit is contained in:
hiperman
2026-02-25 01:06:59 -05:00
parent 386c8042d6
commit 0014659854
+175
View File
@@ -0,0 +1,175 @@
import { triplit, Query } from '../triplit/client';
import type { Habit, HabitCompletion } from '../triplit/schema';
export type CreateHabitData = Omit<Habit, 'id' | 'active' | 'startDate' | 'createdAt' | 'updatedAt'> & {
endDate?: Date;
};
export type UpdateHabitData = Partial<Omit<Habit, 'id' | 'userId' | 'createdAt'>>;
export type CreateCompletionData = Omit<HabitCompletion, 'id' | 'completedAt'> & {
completedAt?: Date;
};
export async function createHabit(data: CreateHabitData & { active?: boolean; startDate?: Date }) {
return await triplit.insert('habits', {
userId: data.userId,
name: data.name,
duration: data.duration,
target: data.target,
increment: data.increment,
unit: data.unit || null,
active: data.active ?? true,
startDate: data.startDate || new Date(),
endDate: data.endDate || null,
createdAt: new Date(),
updatedAt: new Date(),
});
}
export async function updateHabit(id: string, data: UpdateHabitData) {
return await triplit.update('habits', id, {
...data,
updatedAt: new Date(),
});
}
export async function deleteHabit(id: string) {
// Delete all associated completions
const completions = await triplit.fetch(
Query('habit_completions').Where('habitId', '=', id)
);
for (const completion of completions) {
await triplit.delete('habit_completions', completion.id);
}
// Then delete the habit
return await triplit.delete('habits', id);
}
export function getHabits(userId: string) {
return Query('habits')
.Where('userId', '=', userId)
.Order('createdAt', 'ASC');
}
export function getHabit(id: string) {
return Query('habits')
.Where('id', '=', id);
}
export async function createCompletion(data: CreateCompletionData) {
return await triplit.insert('habit_completions', {
habitId: data.habitId,
value: data.value,
failed: data.failed || false,
completedAt: data.completedAt || new Date(),
});
}
export async function deleteCompletion(id: string) {
return await triplit.delete('habit_completions', id);
}
export function getCompletions(habitId: string, startDate?: Date, endDate?: Date) {
let query = Query('habit_completions')
.Where('habitId', '=', habitId);
if (startDate) {
query = query.Where('completedAt', '>=', startDate);
}
if (endDate) {
query = query.Where('completedAt', '<=', endDate);
}
return query.Order('completedAt', 'DESC');
}
// Get habits with completions for a date range
export function getHabitsWithCompletions(userId: string, startDate: Date, endDate: Date) {
return Query('habits')
.Where('userId', '=', userId)
.Where([
{
exists: Query('habit_completions').Where([
['habitId', '=', '$1.id'],
['completedAt', '>=', startDate],
['completedAt', '<=', endDate]
])
}
])
.Include('completions')
.Order('createdAt', 'ASC');
}
export function getHabitCompletions(habitId: string, startDate: Date, endDate: Date) {
return Query('habit_completions')
.Where('habitId', '=', habitId)
.Where('completedAt', '>=', startDate)
.Where('completedAt', '<=', endDate)
.Where('failed', '=', false);
}
export async function archiveHabit(id: string) {
return await updateHabit(id, { active: false });
}
export async function updateCompletion(id: string, data: Partial<Omit<HabitCompletion, 'id' | 'habitId'>>) {
return await triplit.update('habit_completions', id, data);
}
export async function getCompletionByDate(habitId: string, date: Date) {
// Create date range for the specific day (start of day to end of day)
const startOfDay = new Date(date);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(date);
endOfDay.setHours(23, 59, 59, 999);
const completions = await triplit.fetch(
Query('habit_completions')
.Where('habitId', '=', habitId)
.Where('completedAt', '>=', startOfDay)
.Where('completedAt', '<=', endOfDay)
);
return completions[0] || null;
}
export async function upsertCompletion(habitId: string, date: Date, value: number, failed: boolean = false) {
const existing = await getCompletionByDate(habitId, date);
if (existing) {
return await updateCompletion(existing.id, { value, failed });
} else {
return await createCompletion({
habitId,
value,
failed,
completedAt: date
});
}
}
// Reset completions in date range to value 0
export async function resetCompletions(habitId: string, startDate: Date, endDate: Date) {
return await triplit.transact(async (tx) => {
const completions = await tx.fetch(
Query('habit_completions')
.Where('habitId', '=', habitId)
.Where('completedAt', '>=', startDate)
.Where('completedAt', '<=', endDate)
);
for (const completion of completions) {
await tx.update('habit_completions', completion.id, {
value: 0,
failed: false
});
}
return completions.length;
});
}