mirror of
https://github.com/nagaoo0/HabbitGrid.git
synced 2026-01-11 23:44:55 +00:00
uuid validation
This commit is contained in:
@@ -1,3 +1,27 @@
|
|||||||
|
// UUID v4 generator (browser safe)
|
||||||
|
function generateUUID() {
|
||||||
|
if (window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID();
|
||||||
|
// Fallback for older browsers
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||||
|
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// UUID v4 validator
|
||||||
|
function isValidUUID(id) {
|
||||||
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all habits in an array have valid UUIDs (returns new array)
|
||||||
|
function ensureUUIDs(habits) {
|
||||||
|
return habits.map(h => {
|
||||||
|
if (!h.id || !isValidUUID(h.id)) {
|
||||||
|
return { ...h, id: generateUUID() };
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
});
|
||||||
|
}
|
||||||
import { supabase, isSupabaseConfigured } from './supabase';
|
import { supabase, isSupabaseConfigured } from './supabase';
|
||||||
import * as local from './storage';
|
import * as local from './storage';
|
||||||
|
|
||||||
@@ -61,7 +85,10 @@ export async function saveHabit(habit) {
|
|||||||
if (!(await isLoggedIn())) return local.saveHabit(habit);
|
if (!(await isLoggedIn())) return local.saveHabit(habit);
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const { data: auth } = await supabase.auth.getUser();
|
const { data: auth } = await supabase.auth.getUser();
|
||||||
|
// Ensure UUID for new habit
|
||||||
|
const id = habit.id && isValidUUID(habit.id) ? habit.id : generateUUID();
|
||||||
const insert = {
|
const insert = {
|
||||||
|
id,
|
||||||
user_id: auth?.user?.id,
|
user_id: auth?.user?.id,
|
||||||
name: habit.name,
|
name: habit.name,
|
||||||
color: habit.color,
|
color: habit.color,
|
||||||
@@ -76,7 +103,7 @@ export async function saveHabit(habit) {
|
|||||||
const { data, error } = await supabase.from('habits').insert(insert).select('*').single();
|
const { data, error } = await supabase.from('habits').insert(insert).select('*').single();
|
||||||
if (error) {
|
if (error) {
|
||||||
console.warn('Supabase saveHabit error, writing local:', error.message);
|
console.warn('Supabase saveHabit error, writing local:', error.message);
|
||||||
return local.saveHabit(habit);
|
return local.saveHabit({ ...habit, id });
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
@@ -129,11 +156,13 @@ export async function toggleCompletion(habitId, dateStr) {
|
|||||||
|
|
||||||
export async function exportData() {
|
export async function exportData() {
|
||||||
// Always export from local snapshot for portability
|
// Always export from local snapshot for portability
|
||||||
const habits = JSON.parse(localStorage.getItem('habitgrid_data') || '[]');
|
let habits = JSON.parse(localStorage.getItem('habitgrid_data') || '[]');
|
||||||
|
habits = ensureUUIDs(habits);
|
||||||
// If logged in, merge with remote and upsert remote
|
// If logged in, merge with remote and upsert remote
|
||||||
if (await isLoggedIn()) {
|
if (await isLoggedIn()) {
|
||||||
const remote = await getHabits();
|
const remote = await getHabits();
|
||||||
const merged = mergeHabits(habits, remote);
|
let merged = mergeHabits(habits, remote);
|
||||||
|
merged = ensureUUIDs(merged);
|
||||||
await supabase.from('habits').upsert(merged, { onConflict: 'id' });
|
await supabase.from('habits').upsert(merged, { onConflict: 'id' });
|
||||||
return JSON.stringify(merged, null, 2);
|
return JSON.stringify(merged, null, 2);
|
||||||
}
|
}
|
||||||
@@ -143,17 +172,23 @@ export async function exportData() {
|
|||||||
|
|
||||||
export async function importData(jsonString) {
|
export async function importData(jsonString) {
|
||||||
// Import to local
|
// Import to local
|
||||||
const imported = local.importData(jsonString);
|
let imported = local.importData(jsonString);
|
||||||
|
// Always ensure UUIDs for imported data
|
||||||
|
let importedArr = Array.isArray(imported) ? imported : JSON.parse(jsonString);
|
||||||
|
importedArr = ensureUUIDs(importedArr);
|
||||||
// If logged in, merge with remote and upsert
|
// If logged in, merge with remote and upsert
|
||||||
if (await isLoggedIn()) {
|
if (await isLoggedIn()) {
|
||||||
const user = await getAuthUser();
|
const user = await getAuthUser();
|
||||||
const remote = await getHabits();
|
const remote = await getHabits();
|
||||||
const importedArr = Array.isArray(imported) ? imported : JSON.parse(jsonString);
|
let merged = mergeHabits(importedArr, remote);
|
||||||
const merged = mergeHabits(importedArr, remote);
|
merged = ensureUUIDs(merged);
|
||||||
localStorage.setItem('habitgrid_data', JSON.stringify(merged));
|
localStorage.setItem('habitgrid_data', JSON.stringify(merged));
|
||||||
await supabase.from('habits').upsert(merged, { onConflict: 'id' });
|
await supabase.from('habits').upsert(merged, { onConflict: 'id' });
|
||||||
|
return merged;
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('habitgrid_data', JSON.stringify(importedArr));
|
||||||
|
return importedArr;
|
||||||
}
|
}
|
||||||
return imported;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function clearAllData() {
|
export async function clearAllData() {
|
||||||
@@ -172,10 +207,11 @@ export async function syncLocalToRemoteIfNeeded() {
|
|||||||
if (error) return;
|
if (error) return;
|
||||||
|
|
||||||
if (!already || (remote || []).length === 0) {
|
if (!already || (remote || []).length === 0) {
|
||||||
const habits = local.getHabits();
|
let habits = local.getHabits();
|
||||||
if (habits.length === 0) return localStorage.setItem(SYNC_FLAG, new Date().toISOString());
|
if (habits.length === 0) return localStorage.setItem(SYNC_FLAG, new Date().toISOString());
|
||||||
|
habits = ensureUUIDs(habits);
|
||||||
const rows = habits.map(h => ({
|
const rows = habits.map(h => ({
|
||||||
id: h.id && h.id.length > 0 ? h.id : undefined,
|
id: h.id,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
name: h.name,
|
name: h.name,
|
||||||
color: h.color,
|
color: h.color,
|
||||||
@@ -237,14 +273,17 @@ export async function syncRemoteToLocal() {
|
|||||||
|
|
||||||
// If both local and remote have data, merge and update both
|
// If both local and remote have data, merge and update both
|
||||||
if (localHabits.length && remote.length) {
|
if (localHabits.length && remote.length) {
|
||||||
const merged = mergeHabits(localHabits, remote);
|
let merged = mergeHabits(localHabits, remote);
|
||||||
|
merged = ensureUUIDs(merged);
|
||||||
localStorage.setItem('habitgrid_data', JSON.stringify(merged));
|
localStorage.setItem('habitgrid_data', JSON.stringify(merged));
|
||||||
await supabase.from('habits').upsert(merged, { onConflict: 'id' });
|
await supabase.from('habits').upsert(merged, { onConflict: 'id' });
|
||||||
} else if (!remote.length && localHabits.length) {
|
} else if (!remote.length && localHabits.length) {
|
||||||
await supabase.from('habits').upsert(localHabits, { onConflict: 'id' });
|
let ensured = ensureUUIDs(localHabits);
|
||||||
localStorage.setItem('habitgrid_data', JSON.stringify(localHabits));
|
await supabase.from('habits').upsert(ensured, { onConflict: 'id' });
|
||||||
|
localStorage.setItem('habitgrid_data', JSON.stringify(ensured));
|
||||||
} else if (remote.length && !localHabits.length) {
|
} else if (remote.length && !localHabits.length) {
|
||||||
localStorage.setItem('habitgrid_data', JSON.stringify(remote));
|
let ensured = ensureUUIDs(remote);
|
||||||
|
localStorage.setItem('habitgrid_data', JSON.stringify(ensured));
|
||||||
}
|
}
|
||||||
|
|
||||||
window.dispatchEvent(new CustomEvent('habitgrid-sync-updated'));
|
window.dispatchEvent(new CustomEvent('habitgrid-sync-updated'));
|
||||||
|
|||||||
Reference in New Issue
Block a user