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("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() {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
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