Add fallbacks to mock storage for missing tables/unreachable DB; improve resilience
cgen-e7f42cd055f94d6d91d75ade377027ed
This commit is contained in:
parent
5f15ef410b
commit
ff06e4600d
1 changed files with 113 additions and 60 deletions
|
|
@ -1,9 +1,10 @@
|
||||||
// Database adapter for existing AeThex community platform
|
// AeThex Database Adapter
|
||||||
// Maps existing schema to our application needs
|
// Maps existing schema to our application needs
|
||||||
|
|
||||||
import { supabase } from "./supabase";
|
import { supabase } from "./supabase";
|
||||||
import type { Database } from "./database.types";
|
import type { Database } from "./database.types";
|
||||||
import { aethexToast } from "./aethex-toast";
|
import { aethexToast } from "./aethex-toast";
|
||||||
|
import { mockAuth } from "./mock-auth";
|
||||||
|
|
||||||
// Use the existing database user profile type directly
|
// Use the existing database user profile type directly
|
||||||
import type { UserProfile } from "./database.types";
|
import type { UserProfile } from "./database.types";
|
||||||
|
|
@ -53,6 +54,16 @@ export interface AethexUserAchievement {
|
||||||
unlocked_at: string;
|
unlocked_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTableMissing(err: any): boolean {
|
||||||
|
const msg = String(err?.message || err?.hint || err?.details || "");
|
||||||
|
return (
|
||||||
|
err?.code === "42P01" || // undefined_table
|
||||||
|
msg.includes("relation \"") ||
|
||||||
|
msg.includes("does not exist") ||
|
||||||
|
msg.includes("table")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// User Profile Services
|
// User Profile Services
|
||||||
export const aethexUserService = {
|
export const aethexUserService = {
|
||||||
async getCurrentUser(): Promise<AethexUserProfile | null> {
|
async getCurrentUser(): Promise<AethexUserProfile | null> {
|
||||||
|
|
@ -68,18 +79,34 @@ export const aethexUserService = {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error fetching user profile:", error);
|
console.warn("Error fetching user profile, falling back to mock:", error);
|
||||||
return null;
|
const mock = await mockAuth.getUserProfile(user.id as any);
|
||||||
|
if (mock) {
|
||||||
|
return {
|
||||||
|
...(mock as any),
|
||||||
|
email: user.email,
|
||||||
|
} as AethexUserProfile;
|
||||||
|
}
|
||||||
|
const created = await mockAuth.updateProfile(user.id as any, {
|
||||||
|
username: user.email?.split("@")[0] || "user",
|
||||||
|
email: user.email || "",
|
||||||
|
role: "member",
|
||||||
|
onboarded: true,
|
||||||
|
} as any);
|
||||||
|
return {
|
||||||
|
...(created as any),
|
||||||
|
email: user.email,
|
||||||
|
} as AethexUserProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map the existing database fields to our interface
|
// Map the existing database fields to our interface
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
username: data.username || user.email?.split('@')[0] || 'user',
|
username: (data as any).username || user.email?.split("@")[0] || "user",
|
||||||
onboarded: true, // Assume existing users are onboarded
|
onboarded: true,
|
||||||
role: 'member', // Default role
|
role: "member",
|
||||||
loyalty_points: 0, // Default value
|
loyalty_points: 0,
|
||||||
} as AethexUserProfile;
|
} as AethexUserProfile;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -95,7 +122,11 @@ export const aethexUserService = {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error updating profile:", error);
|
console.warn("Error updating profile, attempting mock fallback:", error);
|
||||||
|
if (isTableMissing(error)) {
|
||||||
|
const mock = await mockAuth.updateProfile(userId as any, updates as any);
|
||||||
|
return mock as unknown as AethexUserProfile;
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,12 +143,12 @@ export const aethexUserService = {
|
||||||
.insert({
|
.insert({
|
||||||
id: userId,
|
id: userId,
|
||||||
username: profileData.username || `user_${Date.now()}`,
|
username: profileData.username || `user_${Date.now()}`,
|
||||||
user_type: (profileData.user_type as any) || "community_member",
|
user_type: (profileData as any).user_type || "community_member",
|
||||||
experience_level: (profileData.experience_level as any) || "beginner",
|
experience_level: (profileData as any).experience_level || "beginner",
|
||||||
full_name: profileData.full_name,
|
full_name: profileData.full_name,
|
||||||
bio: profileData.bio,
|
bio: profileData.bio,
|
||||||
location: profileData.location,
|
location: profileData.location,
|
||||||
website_url: profileData.website_url,
|
website_url: (profileData as any).website_url,
|
||||||
github_url: profileData.github_url,
|
github_url: profileData.github_url,
|
||||||
twitter_url: profileData.twitter_url,
|
twitter_url: profileData.twitter_url,
|
||||||
linkedin_url: profileData.linkedin_url,
|
linkedin_url: profileData.linkedin_url,
|
||||||
|
|
@ -130,21 +161,45 @@ export const aethexUserService = {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error creating profile:", error);
|
console.warn("Error creating profile, attempting mock fallback:", error);
|
||||||
|
if (isTableMissing(error)) {
|
||||||
|
const mock = await mockAuth.updateProfile(userId as any, {
|
||||||
|
username: profileData.username || `user_${Date.now()}`,
|
||||||
|
full_name: profileData.full_name,
|
||||||
|
bio: profileData.bio,
|
||||||
|
location: profileData.location,
|
||||||
|
linkedin_url: profileData.linkedin_url as any,
|
||||||
|
github_url: profileData.github_url as any,
|
||||||
|
twitter_url: profileData.twitter_url as any,
|
||||||
|
level: 1,
|
||||||
|
total_xp: 0,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(mock as any),
|
||||||
|
onboarded: true,
|
||||||
|
role: "member",
|
||||||
|
loyalty_points: 0,
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
onboarded: true,
|
onboarded: true,
|
||||||
role: 'member',
|
role: "member",
|
||||||
loyalty_points: 0,
|
loyalty_points: 0,
|
||||||
} as AethexUserProfile;
|
} as AethexUserProfile;
|
||||||
},
|
},
|
||||||
|
|
||||||
async addUserInterests(userId: string, interests: string[]): Promise<void> {
|
async addUserInterests(userId: string, interests: string[]): Promise<void> {
|
||||||
// First, delete existing interests
|
// First, delete existing interests (ignore failures when table missing)
|
||||||
await supabase.from("user_interests").delete().eq("user_id", userId);
|
await supabase
|
||||||
|
.from("user_interests")
|
||||||
|
.delete()
|
||||||
|
.eq("user_id", userId)
|
||||||
|
.catch(() => undefined);
|
||||||
|
|
||||||
// Insert new interests
|
// Insert new interests
|
||||||
const interestRows = interests.map((interest) => ({
|
const interestRows = interests.map((interest) => ({
|
||||||
|
|
@ -152,12 +207,10 @@ export const aethexUserService = {
|
||||||
interest,
|
interest,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase.from("user_interests").insert(interestRows);
|
||||||
.from("user_interests")
|
|
||||||
.insert(interestRows);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error adding interests:", error);
|
if (isTableMissing(error)) return;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -169,11 +222,11 @@ export const aethexUserService = {
|
||||||
.eq("user_id", userId);
|
.eq("user_id", userId);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error fetching interests:", error);
|
console.warn("Error fetching interests:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.map((item) => item.interest);
|
return data.map((item: any) => item.interest);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -187,7 +240,7 @@ export const aethexProjectService = {
|
||||||
.order("created_at", { ascending: false });
|
.order("created_at", { ascending: false });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error fetching projects:", error);
|
console.warn("Error fetching projects:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,7 +257,7 @@ export const aethexProjectService = {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error creating project:", error);
|
console.warn("Error creating project:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,7 +276,7 @@ export const aethexProjectService = {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error updating project:", error);
|
console.warn("Error updating project:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,13 +284,10 @@ export const aethexProjectService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteProject(projectId: string): Promise<boolean> {
|
async deleteProject(projectId: string): Promise<boolean> {
|
||||||
const { error } = await supabase
|
const { error } = await supabase.from("projects").delete().eq("id", projectId);
|
||||||
.from("projects")
|
|
||||||
.delete()
|
|
||||||
.eq("id", projectId);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error deleting project:", error);
|
console.warn("Error deleting project:", error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,7 +312,7 @@ export const aethexProjectService = {
|
||||||
.limit(limit);
|
.limit(limit);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error fetching all projects:", error);
|
console.warn("Error fetching all projects:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,7 +329,7 @@ export const aethexAchievementService = {
|
||||||
.order("xp_reward", { ascending: false });
|
.order("xp_reward", { ascending: false });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error fetching achievements:", error);
|
console.warn("Error fetching achievements:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,12 +348,12 @@ export const aethexAchievementService = {
|
||||||
.eq("user_id", userId);
|
.eq("user_id", userId);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error fetching user achievements:", error);
|
console.warn("Error fetching user achievements:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
return (data as any[])
|
||||||
.map((item) => item.achievements)
|
.map((item) => (item as any).achievements)
|
||||||
.filter(Boolean) as AethexAchievement[];
|
.filter(Boolean) as AethexAchievement[];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -315,7 +365,8 @@ export const aethexAchievementService = {
|
||||||
|
|
||||||
if (error && error.code !== "23505") {
|
if (error && error.code !== "23505") {
|
||||||
// Ignore duplicate key error
|
// Ignore duplicate key error
|
||||||
console.error("Error awarding achievement:", error);
|
if (isTableMissing(error)) return;
|
||||||
|
console.warn("Error awarding achievement:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,12 +380,12 @@ export const aethexAchievementService = {
|
||||||
if (achievement) {
|
if (achievement) {
|
||||||
aethexToast.aethex({
|
aethexToast.aethex({
|
||||||
title: "Achievement Unlocked! 🎉",
|
title: "Achievement Unlocked! 🎉",
|
||||||
description: `${achievement.icon} ${achievement.name} - ${achievement.description}`,
|
description: `${(achievement as any).icon} ${(achievement as any).name} - ${(achievement as any).description}`,
|
||||||
duration: 8000,
|
duration: 8000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update user's total XP and level
|
// Update user's total XP and level
|
||||||
await this.updateUserXPAndLevel(userId, achievement.xp_reward);
|
await this.updateUserXPAndLevel(userId, (achievement as any).xp_reward);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -347,6 +398,7 @@ export const aethexAchievementService = {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error || !profile) {
|
if (error || !profile) {
|
||||||
|
if (isTableMissing(error)) return;
|
||||||
console.log("Profile not found or missing XP fields, skipping XP update");
|
console.log("Profile not found or missing XP fields, skipping XP update");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -357,15 +409,12 @@ export const aethexAchievementService = {
|
||||||
|
|
||||||
// Update profile (only update existing fields)
|
// Update profile (only update existing fields)
|
||||||
const updates: any = {};
|
const updates: any = {};
|
||||||
if ('total_xp' in profile) updates.total_xp = newTotalXP;
|
if ("total_xp" in (profile as any)) updates.total_xp = newTotalXP;
|
||||||
if ('level' in profile) updates.level = newLevel;
|
if ("level" in (profile as any)) updates.level = newLevel;
|
||||||
if ('loyalty_points' in profile) updates.loyalty_points = newLoyaltyPoints;
|
if ("loyalty_points" in (profile as any)) updates.loyalty_points = newLoyaltyPoints;
|
||||||
|
|
||||||
if (Object.keys(updates).length > 0) {
|
if (Object.keys(updates).length > 0) {
|
||||||
await supabase
|
await supabase.from("user_profiles").update(updates).eq("id", userId);
|
||||||
.from("user_profiles")
|
|
||||||
.update(updates)
|
|
||||||
.eq("id", userId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for level-up achievements
|
// Check for level-up achievements
|
||||||
|
|
@ -378,7 +427,7 @@ export const aethexAchievementService = {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (levelUpAchievement.data) {
|
if (levelUpAchievement.data) {
|
||||||
await this.awardAchievement(userId, levelUpAchievement.data.id);
|
await this.awardAchievement(userId, (levelUpAchievement.data as any).id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -392,7 +441,7 @@ export const aethexAchievementService = {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (achievement) {
|
if (achievement) {
|
||||||
await this.awardAchievement(userId, achievement.id);
|
await this.awardAchievement(userId, (achievement as any).id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -408,7 +457,7 @@ export const aethexAchievementService = {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (achievement) {
|
if (achievement) {
|
||||||
await this.awardAchievement(userId, achievement.id);
|
await this.awardAchievement(userId, (achievement as any).id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,7 +471,7 @@ export const aethexAchievementService = {
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (achievement) {
|
if (achievement) {
|
||||||
await this.awardAchievement(userId, achievement.id);
|
await this.awardAchievement(userId, (achievement as any).id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -439,18 +488,20 @@ export const aethexNotificationService = {
|
||||||
.limit(10);
|
.limit(10);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error fetching notifications:", error);
|
console.warn("Error fetching notifications:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data as any[];
|
||||||
},
|
},
|
||||||
|
|
||||||
async markAsRead(notificationId: string): Promise<void> {
|
async markAsRead(notificationId: string): Promise<void> {
|
||||||
await supabase
|
try {
|
||||||
.from("notifications")
|
await supabase
|
||||||
.update({ read: true })
|
.from("notifications")
|
||||||
.eq("id", notificationId);
|
.update({ read: true })
|
||||||
|
.eq("id", notificationId);
|
||||||
|
} catch {}
|
||||||
},
|
},
|
||||||
|
|
||||||
async createNotification(
|
async createNotification(
|
||||||
|
|
@ -459,12 +510,14 @@ export const aethexNotificationService = {
|
||||||
title: string,
|
title: string,
|
||||||
message: string,
|
message: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await supabase.from("notifications").insert({
|
try {
|
||||||
user_id: userId,
|
await supabase.from("notifications").insert({
|
||||||
type,
|
user_id: userId,
|
||||||
title,
|
type,
|
||||||
message,
|
title,
|
||||||
});
|
message,
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue