tasks now update streaks in db
This commit is contained in:
@@ -69,17 +69,20 @@ export async function getDailyEntryForToday(): Promise<DailyQuizSubmission | nul
|
||||
)
|
||||
.eq("user_id", userId)
|
||||
.eq("entry_date", entryDate)
|
||||
.maybeSingle();
|
||||
.order("created_at", { ascending: false })
|
||||
.limit(1);
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
const row = data?.[0];
|
||||
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mapRowToSubmission(data as DailyEntryRow);
|
||||
return mapRowToSubmission(row as DailyEntryRow);
|
||||
}
|
||||
|
||||
export async function getDailyQuizSubmission() {
|
||||
|
||||
@@ -50,12 +50,50 @@ function mapHabitRow(row: HabitRow): Habit {
|
||||
id: row.id,
|
||||
label: row.name,
|
||||
catalogId: getCatalogIdByLabel(row.name),
|
||||
streak: row.streak,
|
||||
lastCompletedDate: row.last_completed_on,
|
||||
streak: row.streak ?? 0,
|
||||
lastCompletedDate: row.last_completed_on ?? null,
|
||||
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() {
|
||||
const supabase = createClient();
|
||||
const {
|
||||
@@ -111,14 +149,13 @@ async function getPreviousCompletedDate(
|
||||
.eq("habit_id", habitId)
|
||||
.neq("completed_on", today)
|
||||
.order("completed_on", { ascending: false })
|
||||
.limit(1)
|
||||
.maybeSingle();
|
||||
.limit(1);
|
||||
|
||||
if (error) {
|
||||
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()) {
|
||||
@@ -144,7 +181,9 @@ export async function getHabits(): Promise<Habit[]> {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
const rows = await reconcileMissedDayStreaks((data ?? []) as HabitRow[]);
|
||||
const rows = await reconcileMissedDayStreaks(
|
||||
dedupeHabitsByName((data ?? []) as HabitRow[]),
|
||||
);
|
||||
|
||||
return rows.map(mapHabitRow);
|
||||
}
|
||||
@@ -163,19 +202,7 @@ export async function ensureCatalogHabit(catalogId: string): Promise<Habit | nul
|
||||
}
|
||||
|
||||
const supabase = createClient();
|
||||
const { data: existing, error: existingError } = await supabase
|
||||
.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;
|
||||
let row = await findActiveHabitByName(userId, entry.label);
|
||||
|
||||
if (!row) {
|
||||
const { data: inserted, error: insertError } = await supabase
|
||||
@@ -190,11 +217,15 @@ export async function ensureCatalogHabit(catalogId: string): Promise<Habit | nul
|
||||
.single();
|
||||
|
||||
if (insertError) {
|
||||
row = await findActiveHabitByName(userId, entry.label);
|
||||
|
||||
if (!row) {
|
||||
throw new Error(insertError.message);
|
||||
}
|
||||
|
||||
} else {
|
||||
row = inserted as HabitRow;
|
||||
}
|
||||
}
|
||||
|
||||
const [reconciledRow] = await reconcileMissedDayStreaks([row]);
|
||||
return mapHabitRow(reconciledRow);
|
||||
@@ -228,23 +259,26 @@ export async function toggleHabitCompletion(habitId: string): Promise<Habit[]> {
|
||||
const [reconciledHabit] = await reconcileMissedDayStreaks([habit as HabitRow]);
|
||||
const currentStreak = reconciledHabit.streak;
|
||||
|
||||
const { data: existingLog, error: existingLogError } = await supabase
|
||||
const { data: existingLogs, error: existingLogError } = await supabase
|
||||
.from("habit_logs")
|
||||
.select("id")
|
||||
.eq("user_id", userId)
|
||||
.eq("habit_id", habitId)
|
||||
.eq("completed_on", today)
|
||||
.maybeSingle();
|
||||
.eq("completed_on", today);
|
||||
|
||||
if (existingLogError) {
|
||||
throw new Error(existingLogError.message);
|
||||
}
|
||||
|
||||
const existingLog = getFirstRow(existingLogs as { id: string }[] | null);
|
||||
|
||||
if (existingLog) {
|
||||
const { error: deleteError } = await supabase
|
||||
.from("habit_logs")
|
||||
.delete()
|
||||
.eq("id", existingLog.id);
|
||||
.eq("user_id", userId)
|
||||
.eq("habit_id", habitId)
|
||||
.eq("completed_on", today);
|
||||
|
||||
if (deleteError) {
|
||||
throw new Error(deleteError.message);
|
||||
|
||||
13
supabase/migrations/20260524160000_deduplicate_habits.sql
Normal file
13
supabase/migrations/20260524160000_deduplicate_habits.sql
Normal 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;
|
||||
Reference in New Issue
Block a user