// AeThex Database Adapter // Maps existing schema to our application needs import { supabase } from "./supabase"; import type { Database } from "./database.types"; import { aethexToast } from "./aethex-toast"; import { mockAuth } from "./mock-auth"; // Use the existing database user profile type directly import type { UserProfile } from "./database.types"; // Extended type that matches the existing shared database export interface AethexUserProfile extends UserProfile { email?: string; username: string | null; onboarded?: boolean; role?: string; loyalty_points?: number; banner_url?: string; social_links?: any; skills?: string[]; } export interface AethexProject { id: string; user_id: string; title: string; description?: string; status: "planning" | "in_progress" | "completed" | "on_hold"; technologies?: string[]; github_url?: string; live_url?: string; image_url?: string; start_date?: string; end_date?: string; created_at: string; updated_at: string; } export interface AethexAchievement { id: string; name: string; description: string; icon?: string; xp_reward: number; badge_color?: string; created_at: string; } export interface AethexUserAchievement { id: string; user_id: string; achievement_id: 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 export const aethexUserService = { async getCurrentUser(): Promise { const { data: { user }, } = await supabase.auth.getUser(); if (!user) return null; const { data, error } = await supabase .from("user_profiles") .select("*") .eq("id", user.id) .single(); if (error) { console.warn("Error fetching user profile, falling back to mock:", error); 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 return { ...data, email: user.email, username: (data as any).username || user.email?.split("@")[0] || "user", onboarded: true, role: "member", loyalty_points: 0, } as AethexUserProfile; }, async updateProfile( userId: string, updates: Partial, ): Promise { const { data, error } = await supabase .from("user_profiles") .update(updates) .eq("id", userId) .select() .single(); if (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; } return data as AethexUserProfile; }, async createInitialProfile( userId: string, profileData: Partial, ): Promise { // Only insert fields that exist in the actual database schema const { data, error } = await supabase .from("user_profiles") .insert({ id: userId, username: profileData.username || `user_${Date.now()}`, user_type: (profileData as any).user_type || "community_member", experience_level: (profileData as any).experience_level || "beginner", full_name: profileData.full_name, bio: profileData.bio, location: profileData.location, website_url: (profileData as any).website_url, github_url: profileData.github_url, twitter_url: profileData.twitter_url, linkedin_url: profileData.linkedin_url, level: 1, total_xp: 0, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }) .select() .single(); if (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; } return { ...data, onboarded: true, role: "member", loyalty_points: 0, } as AethexUserProfile; }, async addUserInterests(userId: string, interests: string[]): Promise { // First, delete existing interests (ignore failures when table missing) await supabase .from("user_interests") .delete() .eq("user_id", userId) .catch(() => undefined); // Insert new interests const interestRows = interests.map((interest) => ({ user_id: userId, interest, })); const { error } = await supabase.from("user_interests").insert(interestRows); if (error) { if (isTableMissing(error)) return; throw error; } }, async getUserInterests(userId: string): Promise { const { data, error } = await supabase .from("user_interests") .select("interest") .eq("user_id", userId); if (error) { console.warn("Error fetching interests:", error); return []; } return data.map((item: any) => item.interest); }, }; // Project Services export const aethexProjectService = { async getUserProjects(userId: string): Promise { const { data, error } = await supabase .from("projects") .select("*") .eq("user_id", userId) .order("created_at", { ascending: false }); if (error) { console.warn("Error fetching projects:", error); return []; } return data as AethexProject[]; }, async createProject( project: Omit, ): Promise { const { data, error } = await supabase .from("projects") .insert(project) .select() .single(); if (error) { console.warn("Error creating project:", error); throw error; } return data as AethexProject; }, async updateProject( projectId: string, updates: Partial, ): Promise { const { data, error } = await supabase .from("projects") .update(updates) .eq("id", projectId) .select() .single(); if (error) { console.warn("Error updating project:", error); throw error; } return data as AethexProject; }, async deleteProject(projectId: string): Promise { const { error } = await supabase.from("projects").delete().eq("id", projectId); if (error) { console.warn("Error deleting project:", error); return false; } return true; }, async getAllProjects(limit = 10): Promise { const { data, error } = await supabase .from("projects") .select( ` *, profiles!projects_user_id_fkey ( username, full_name, avatar_url ) `, ) .eq("status", "completed") .order("created_at", { ascending: false }) .limit(limit); if (error) { console.warn("Error fetching all projects:", error); return []; } return data as AethexProject[]; }, }; // Achievement Services (maps to existing achievements table) export const aethexAchievementService = { async getAllAchievements(): Promise { const { data, error } = await supabase .from("achievements") .select("*") .order("xp_reward", { ascending: false }); if (error) { console.warn("Error fetching achievements:", error); return []; } return data as AethexAchievement[]; }, async getUserAchievements(userId: string): Promise { const { data, error } = await supabase .from("user_achievements") .select( ` achievement_id, achievements (*) `, ) .eq("user_id", userId); if (error) { console.warn("Error fetching user achievements:", error); return []; } return (data as any[]) .map((item) => (item as any).achievements) .filter(Boolean) as AethexAchievement[]; }, async awardAchievement(userId: string, achievementId: string): Promise { const { error } = await supabase.from("user_achievements").insert({ user_id: userId, achievement_id: achievementId, }); if (error && error.code !== "23505") { // Ignore duplicate key error if (isTableMissing(error)) return; console.warn("Error awarding achievement:", error); throw error; } // Get achievement details for toast const { data: achievement } = await supabase .from("achievements") .select("*") .eq("id", achievementId) .single(); if (achievement) { aethexToast.aethex({ title: "Achievement Unlocked! 🎉", description: `${(achievement as any).icon} ${(achievement as any).name} - ${(achievement as any).description}`, duration: 8000, }); // Update user's total XP and level await this.updateUserXPAndLevel(userId, (achievement as any).xp_reward); } }, async updateUserXPAndLevel(userId: string, xpGained: number): Promise { // Get current user data const { data: profile, error } = await supabase .from("user_profiles") .select("total_xp, level, loyalty_points") .eq("id", userId) .single(); if (error || !profile) { if (isTableMissing(error)) return; console.log("Profile not found or missing XP fields, skipping XP update"); return; } const newTotalXP = ((profile as any).total_xp || 0) + xpGained; const newLevel = Math.floor(newTotalXP / 1000) + 1; // 1000 XP per level const newLoyaltyPoints = ((profile as any).loyalty_points || 0) + xpGained; // Update profile (only update existing fields) const updates: any = {}; if ("total_xp" in (profile as any)) updates.total_xp = newTotalXP; if ("level" in (profile as any)) updates.level = newLevel; if ("loyalty_points" in (profile as any)) updates.loyalty_points = newLoyaltyPoints; if (Object.keys(updates).length > 0) { await supabase.from("user_profiles").update(updates).eq("id", userId); } // Check for level-up achievements if (newLevel > ((profile as any).level || 1)) { if (newLevel >= 5) { const levelUpAchievement = await supabase .from("achievements") .select("id") .eq("name", "Level Master") .single(); if (levelUpAchievement.data) { await this.awardAchievement(userId, (levelUpAchievement.data as any).id); } } } }, async checkAndAwardOnboardingAchievement(userId: string): Promise { const { data: achievement } = await supabase .from("achievements") .select("id") .eq("name", "AeThex Explorer") .single(); if (achievement) { await this.awardAchievement(userId, (achievement as any).id); } }, async checkAndAwardProjectAchievements(userId: string): Promise { const projects = await aethexProjectService.getUserProjects(userId); // First project achievement if (projects.length >= 1) { const { data: achievement } = await supabase .from("achievements") .select("id") .eq("name", "Portfolio Creator") .single(); if (achievement) { await this.awardAchievement(userId, (achievement as any).id); } } // Project master achievement const completedProjects = projects.filter((p) => p.status === "completed"); if (completedProjects.length >= 10) { const { data: achievement } = await supabase .from("achievements") .select("id") .eq("name", "Project Master") .single(); if (achievement) { await this.awardAchievement(userId, (achievement as any).id); } } }, }; // Notification Service (uses existing notifications table) export const aethexNotificationService = { async getUserNotifications(userId: string): Promise { const { data, error } = await supabase .from("notifications") .select("*") .eq("user_id", userId) .order("created_at", { ascending: false }) .limit(10); if (error) { console.warn("Error fetching notifications:", error); return []; } return data as any[]; }, async markAsRead(notificationId: string): Promise { try { await supabase .from("notifications") .update({ read: true }) .eq("id", notificationId); } catch {} }, async createNotification( userId: string, type: string, title: string, message: string, ): Promise { try { await supabase.from("notifications").insert({ user_id: userId, type, title, message, }); } catch {} }, }; // Real-time subscriptions export const aethexRealtimeService = { subscribeToUserNotifications( userId: string, callback: (notification: any) => void, ) { return supabase .channel(`notifications:${userId}`) .on( "postgres_changes", { event: "INSERT", schema: "public", table: "notifications", filter: `user_id=eq.${userId}`, }, callback, ) .subscribe(); }, subscribeToProjects(callback: (project: any) => void) { return supabase .channel("projects") .on( "postgres_changes", { event: "INSERT", schema: "public", table: "projects", }, callback, ) .subscribe(); }, };