From cd39dc06c5b358fae6862310a142dc2ad8ecdbaf Mon Sep 17 00:00:00 2001 From: MrPiglr <31398225+MrPiglr@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:28:49 -0700 Subject: [PATCH] Add shared update guard and reliable site deletion count --- server/storage.ts | 199 ++++++++++++++++++++++++++++------------------ 1 file changed, 121 insertions(+), 78 deletions(-) diff --git a/server/storage.ts b/server/storage.ts index 282fca3..d42c220 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -1,4 +1,12 @@ -import { type Profile, type Project, type ChatMessage } from "../shared/schema.js"; +import { + type Profile, + type Project, + type ChatMessage, + type AethexSite, + type Application, + type Achievement, + type AethexAlert, +} from "../shared/schema.js"; import { supabase } from "./supabase.js"; export interface IStorage { @@ -8,27 +16,30 @@ export interface IStorage { getProfileByUsername(username: string): Promise; updateProfile(id: string, data: Partial): Promise; getLeadershipProfiles(): Promise; - + // Projects getProjects(): Promise; getProject(id: string): Promise; - + // Sites - getSites(): Promise; - + getSites(): Promise; + createSite(site: Partial): Promise; + updateSite(id: string, updates: Partial): Promise; + deleteSite(id: string): Promise; + // Auth Logs getAuthLogs(): Promise; - + // Achievements - getAchievements(): Promise; - + getAchievements(): Promise; + // Applications - getApplications(): Promise; - updateApplication(id: string, updates: any): Promise; - + getApplications(): Promise; + updateApplication(id: string, updates: Partial): Promise; + // Alerts - getAlerts(): Promise; - updateAlert(id: string, updates: any): Promise; + getAlerts(): Promise; + updateAlert(id: string, updates: Partial): Promise; // Notifications (for WebSocket) getNotifications(): Promise; @@ -50,38 +61,54 @@ export interface IStorage { } export class SupabaseStorage implements IStorage { - // Create a new site - async createSite(site: any): Promise { - const { data, error } = await supabase - .from('aethex_sites') - .insert(site) - .select() - .single(); - if (error) throw new Error(error.message); - return data; - } + private filterDefined>(updates: Partial): Partial { + return Object.fromEntries( + Object.entries(updates).filter(([, value]) => value !== undefined) + ) as Partial; + } - // Update a site - async updateSite(id: string, updates: any): Promise { - const { data, error } = await supabase - .from('aethex_sites') - .update({ ...updates, updated_at: new Date().toISOString() }) - .eq('id', id) - .select() - .single(); - if (error) throw new Error(error.message); - return data; + private ensureUpdates>(updates: Partial, entity: string): asserts updates is Partial & Record { + if (Object.keys(updates).length === 0) { + throw new Error(`No ${entity} fields provided for update`); } + } - // Delete a site - async deleteSite(id: string): Promise { - const { error, count } = await supabase - .from('aethex_sites') - .delete({ count: 'exact' }) - .eq('id', id); - if (error) throw new Error(error.message); - return (count ?? 0) > 0; - } + // Create a new site + async createSite(site: Partial): Promise { + const cleanSite = this.filterDefined(site); + const { data, error } = await supabase + .from('aethex_sites') + .insert(cleanSite) + .select() + .single(); + if (error) throw new Error(error.message); + return data as AethexSite; + } + + // Update a site + async updateSite(id: string, updates: Partial): Promise { + const cleanUpdates = this.filterDefined(updates); + this.ensureUpdates(cleanUpdates, 'site'); + const { data, error } = await supabase + .from('aethex_sites') + .update({ ...cleanUpdates, updated_at: new Date().toISOString() }) + .eq('id', id) + .select() + .single(); + if (error) throw new Error(error.message); + return data as AethexSite; + } + + // Delete a site + async deleteSite(id: string): Promise { + const { error, data } = await supabase + .from('aethex_sites') + .delete() + .eq('id', id) + .select('id'); + if (error) throw new Error(error.message); + return (data?.length ?? 0) > 0; + } async getProfiles(): Promise { const { data, error } = await supabase @@ -116,16 +143,28 @@ export class SupabaseStorage implements IStorage { } async updateProfile(id: string, updates: Partial): Promise { + const cleanUpdates = this.filterDefined(updates); + this.ensureUpdates(cleanUpdates, 'profile'); const { data, error } = await supabase .from('profiles') - .update({ ...updates, updated_at: new Date().toISOString() }) + .update({ ...cleanUpdates, updated_at: new Date().toISOString() }) .eq('id', id) .select() .single(); - + if (error || !data) return undefined; return data as Profile; } + + async getLeadershipProfiles(): Promise { + const { data, error } = await supabase + .from('profiles') + .select('*') + .in('role', ['oversee', 'admin']); + + if (error || !data) return []; + return data as Profile[]; + } async getProjects(): Promise { const { data, error } = await supabase @@ -148,16 +187,16 @@ export class SupabaseStorage implements IStorage { return data as Project; } - async getSites(): Promise { + async getSites(): Promise { const { data, error } = await supabase .from('aethex_sites') .select('*') .order('last_check', { ascending: false }); - - if (error) return []; - return data || []; + + if (error || !data) return []; + return data as AethexSite[]; } - + async getAuthLogs(): Promise { const { data, error } = await supabase .from('auth_logs') @@ -169,43 +208,46 @@ export class SupabaseStorage implements IStorage { return data || []; } - async getAchievements(): Promise { + async getAchievements(): Promise { const { data, error } = await supabase .from('achievements') .select('*') .order('name', { ascending: true }); - - if (error) return []; - return data || []; + + if (error || !data) return []; + return data as Achievement[]; } - - async getApplications(): Promise { + + async getApplications(): Promise { const { data, error } = await supabase .from('applications') .select('*') .order('submitted_at', { ascending: false }); - - if (error) return []; - return data || []; + + if (error || !data) return []; + return data as Application[]; } - - async getAlerts(): Promise { + + async getAlerts(): Promise { const { data, error } = await supabase .from('aethex_alerts') .select('*') .order('created_at', { ascending: false }) .limit(50); - - if (error) return []; - return data || []; + + if (error || !data) return []; + return data as AethexAlert[]; } - async updateAlert(id: string, updates: any): Promise { - const updateData: any = {}; - if ('is_resolved' in updates) { - updateData.is_resolved = updates.is_resolved; - } - + async updateAlert(id: string, updates: Partial): Promise { + const updateData = this.filterDefined({ + message: updates.message, + severity: updates.severity, + is_resolved: updates.is_resolved, + resolved_at: updates.resolved_at, + }); + this.ensureUpdates(updateData, 'alert'); + const { data, error } = await supabase .from('aethex_alerts') .update(updateData) @@ -217,15 +259,16 @@ export class SupabaseStorage implements IStorage { console.error('Update alert error:', error); throw new Error(error.message); } - return data; + return data as AethexAlert; } - - async updateApplication(id: string, updates: any): Promise { - const updateData: any = {}; - if ('status' in updates) { - updateData.status = updates.status; - } - + + async updateApplication(id: string, updates: Partial): Promise { + const updateData = this.filterDefined({ + status: updates.status, + response_message: updates.response_message, + }); + this.ensureUpdates(updateData, 'application'); + const { data, error } = await supabase .from('applications') .update(updateData) @@ -237,7 +280,7 @@ export class SupabaseStorage implements IStorage { console.error('Update application error:', error); throw new Error(error.message); } - return data; + return data as Application; } // Chat Messages (AI memory)