tasks now update streaks in db

This commit is contained in:
masongga
2026-05-23 22:12:00 -06:00
parent f51b73bec4
commit 53f3cec385
3 changed files with 79 additions and 29 deletions

View File

@@ -69,17 +69,20 @@ export async function getDailyEntryForToday(): Promise<DailyQuizSubmission | nul
) )
.eq("user_id", userId) .eq("user_id", userId)
.eq("entry_date", entryDate) .eq("entry_date", entryDate)
.maybeSingle(); .order("created_at", { ascending: false })
.limit(1);
if (error) { if (error) {
throw new Error(error.message); throw new Error(error.message);
} }
if (!data) { const row = data?.[0];
if (!row) {
return null; return null;
} }
return mapRowToSubmission(data as DailyEntryRow); return mapRowToSubmission(row as DailyEntryRow);
} }
export async function getDailyQuizSubmission() { export async function getDailyQuizSubmission() {

View File

@@ -50,12 +50,50 @@ function mapHabitRow(row: HabitRow): Habit {
id: row.id, id: row.id,
label: row.name, label: row.name,
catalogId: getCatalogIdByLabel(row.name), catalogId: getCatalogIdByLabel(row.name),
streak: row.streak, streak: row.streak ?? 0,
lastCompletedDate: row.last_completed_on, lastCompletedDate: row.last_completed_on ?? null,
isCustom: !isCatalogLabel(row.name), isCustom: !isCatalogLabel(row.name),
}; };
} }
function getFirstRow<T>(rows: T[] | null | undefined) {
return rows?.[0] ?? null;
}
function dedupeHabitsByName(rows: HabitRow[]) {
const seen = new Map<string, HabitRow>();
for (const row of rows) {
const key = row.name.trim().toLowerCase();
if (!seen.has(key)) {
seen.set(key, row);
}
}
return Array.from(seen.values());
}
async function findActiveHabitByName(
userId: string,
name: string,
): Promise<HabitRow | null> {
const supabase = createClient();
const { data, error } = await supabase
.from("habits")
.select("id, name, streak, last_completed_on")
.eq("user_id", userId)
.eq("name", name)
.eq("active", true)
.order("created_at", { ascending: true });
if (error) {
throw new Error(error.message);
}
return getFirstRow(data as HabitRow[] | null);
}
async function getAuthenticatedUserId() { async function getAuthenticatedUserId() {
const supabase = createClient(); const supabase = createClient();
const { const {
@@ -111,14 +149,13 @@ async function getPreviousCompletedDate(
.eq("habit_id", habitId) .eq("habit_id", habitId)
.neq("completed_on", today) .neq("completed_on", today)
.order("completed_on", { ascending: false }) .order("completed_on", { ascending: false })
.limit(1) .limit(1);
.maybeSingle();
if (error) { if (error) {
throw new Error(error.message); throw new Error(error.message);
} }
return data?.completed_on ?? null; return getFirstRow(data as { completed_on: string }[] | null)?.completed_on ?? null;
} }
export function isHabitCompletedToday(habit: Habit, today = getTodayDateKey()) { export function isHabitCompletedToday(habit: Habit, today = getTodayDateKey()) {
@@ -144,7 +181,9 @@ export async function getHabits(): Promise<Habit[]> {
throw new Error(error.message); throw new Error(error.message);
} }
const rows = await reconcileMissedDayStreaks((data ?? []) as HabitRow[]); const rows = await reconcileMissedDayStreaks(
dedupeHabitsByName((data ?? []) as HabitRow[]),
);
return rows.map(mapHabitRow); return rows.map(mapHabitRow);
} }
@@ -163,19 +202,7 @@ export async function ensureCatalogHabit(catalogId: string): Promise<Habit | nul
} }
const supabase = createClient(); const supabase = createClient();
const { data: existing, error: existingError } = await supabase let row = await findActiveHabitByName(userId, entry.label);
.from("habits")
.select("id, name, streak, last_completed_on")
.eq("user_id", userId)
.eq("name", entry.label)
.eq("active", true)
.maybeSingle();
if (existingError) {
throw new Error(existingError.message);
}
let row = existing as HabitRow | null;
if (!row) { if (!row) {
const { data: inserted, error: insertError } = await supabase const { data: inserted, error: insertError } = await supabase
@@ -190,11 +217,15 @@ export async function ensureCatalogHabit(catalogId: string): Promise<Habit | nul
.single(); .single();
if (insertError) { if (insertError) {
row = await findActiveHabitByName(userId, entry.label);
if (!row) {
throw new Error(insertError.message); throw new Error(insertError.message);
} }
} else {
row = inserted as HabitRow; row = inserted as HabitRow;
} }
}
const [reconciledRow] = await reconcileMissedDayStreaks([row]); const [reconciledRow] = await reconcileMissedDayStreaks([row]);
return mapHabitRow(reconciledRow); return mapHabitRow(reconciledRow);
@@ -228,23 +259,26 @@ export async function toggleHabitCompletion(habitId: string): Promise<Habit[]> {
const [reconciledHabit] = await reconcileMissedDayStreaks([habit as HabitRow]); const [reconciledHabit] = await reconcileMissedDayStreaks([habit as HabitRow]);
const currentStreak = reconciledHabit.streak; const currentStreak = reconciledHabit.streak;
const { data: existingLog, error: existingLogError } = await supabase const { data: existingLogs, error: existingLogError } = await supabase
.from("habit_logs") .from("habit_logs")
.select("id") .select("id")
.eq("user_id", userId) .eq("user_id", userId)
.eq("habit_id", habitId) .eq("habit_id", habitId)
.eq("completed_on", today) .eq("completed_on", today);
.maybeSingle();
if (existingLogError) { if (existingLogError) {
throw new Error(existingLogError.message); throw new Error(existingLogError.message);
} }
const existingLog = getFirstRow(existingLogs as { id: string }[] | null);
if (existingLog) { if (existingLog) {
const { error: deleteError } = await supabase const { error: deleteError } = await supabase
.from("habit_logs") .from("habit_logs")
.delete() .delete()
.eq("id", existingLog.id); .eq("user_id", userId)
.eq("habit_id", habitId)
.eq("completed_on", today);
if (deleteError) { if (deleteError) {
throw new Error(deleteError.message); throw new Error(deleteError.message);

View File

@@ -0,0 +1,13 @@
-- Remove duplicate active habits with the same name for a user.
delete from public.habits duplicate
using public.habits keeper
where duplicate.user_id = keeper.user_id
and duplicate.name = keeper.name
and duplicate.active = true
and keeper.active = true
and duplicate.id <> keeper.id
and duplicate.created_at > keeper.created_at;
create unique index if not exists habits_user_id_name_active_unique
on public.habits (user_id, name)
where active = true;