Create database adapter for existing AeThex schema
cgen-3c850819cf154266a319eebb1dbea822
This commit is contained in:
parent
af01ad03e7
commit
931158d9dd
1 changed files with 470 additions and 0 deletions
470
client/lib/aethex-database-adapter.ts
Normal file
470
client/lib/aethex-database-adapter.ts
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
// Database adapter for existing AeThex community platform
|
||||
// Maps existing schema to our application needs
|
||||
|
||||
import { supabase } from './supabase';
|
||||
import type { Database } from './database.types';
|
||||
import { aethexToast } from './aethex-toast';
|
||||
|
||||
// Extended user profile type that matches existing + new schema
|
||||
export interface AethexUserProfile {
|
||||
id: string;
|
||||
username: string;
|
||||
email?: string;
|
||||
role: string;
|
||||
onboarded: boolean;
|
||||
bio?: string;
|
||||
skills?: string[];
|
||||
avatar_url?: string;
|
||||
banner_url?: string;
|
||||
social_links?: any;
|
||||
loyalty_points: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
// New AeThex app fields
|
||||
user_type?: 'game_developer' | 'client' | 'community_member' | 'customer';
|
||||
experience_level?: 'beginner' | 'intermediate' | 'advanced' | 'expert';
|
||||
full_name?: string;
|
||||
location?: string;
|
||||
website_url?: string;
|
||||
github_url?: string;
|
||||
twitter_url?: string;
|
||||
linkedin_url?: string;
|
||||
total_xp?: number;
|
||||
level?: number;
|
||||
}
|
||||
|
||||
export interface AethexProject {
|
||||
id: string;
|
||||
user_id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
status: 'planning' | 'in_progress' | 'completed' | 'on_hold';
|
||||
technologies?: string[];
|
||||
github_url?: string;
|
||||
demo_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;
|
||||
points_reward: number;
|
||||
badge_color?: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface AethexUserAchievement {
|
||||
id: string;
|
||||
user_id: string;
|
||||
achievement_id: string;
|
||||
unlocked_at: string;
|
||||
}
|
||||
|
||||
// User Profile Services
|
||||
export const aethexUserService = {
|
||||
async getCurrentUser(): Promise<AethexUserProfile | null> {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) return null;
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching user profile:', error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data as AethexUserProfile;
|
||||
},
|
||||
|
||||
async updateProfile(userId: string, updates: Partial<AethexUserProfile>): Promise<AethexUserProfile | null> {
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.update(updates)
|
||||
.eq('id', userId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error('Error updating profile:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data as AethexUserProfile;
|
||||
},
|
||||
|
||||
async createInitialProfile(userId: string, profileData: Partial<AethexUserProfile>): Promise<AethexUserProfile | null> {
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.insert({
|
||||
id: userId,
|
||||
username: profileData.username || `user_${Date.now()}`,
|
||||
user_type: profileData.user_type || 'community_member',
|
||||
experience_level: profileData.experience_level || 'beginner',
|
||||
full_name: profileData.full_name,
|
||||
email: profileData.email,
|
||||
...profileData
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error('Error creating profile:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data as AethexUserProfile;
|
||||
},
|
||||
|
||||
async addUserInterests(userId: string, interests: string[]): Promise<void> {
|
||||
// First, delete existing interests
|
||||
await supabase
|
||||
.from('user_interests')
|
||||
.delete()
|
||||
.eq('user_id', userId);
|
||||
|
||||
// Insert new interests
|
||||
const interestRows = interests.map(interest => ({
|
||||
user_id: userId,
|
||||
interest,
|
||||
}));
|
||||
|
||||
const { error } = await supabase
|
||||
.from('user_interests')
|
||||
.insert(interestRows);
|
||||
|
||||
if (error) {
|
||||
console.error('Error adding interests:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getUserInterests(userId: string): Promise<string[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('user_interests')
|
||||
.select('interest')
|
||||
.eq('user_id', userId);
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching interests:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.map(item => item.interest);
|
||||
},
|
||||
};
|
||||
|
||||
// Project Services
|
||||
export const aethexProjectService = {
|
||||
async getUserProjects(userId: string): Promise<AethexProject[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching projects:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data as AethexProject[];
|
||||
},
|
||||
|
||||
async createProject(project: Omit<AethexProject, 'id' | 'created_at' | 'updated_at'>): Promise<AethexProject | null> {
|
||||
const { data, error } = await supabase
|
||||
.from('projects')
|
||||
.insert(project)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error('Error creating project:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data as AethexProject;
|
||||
},
|
||||
|
||||
async updateProject(projectId: string, updates: Partial<AethexProject>): Promise<AethexProject | null> {
|
||||
const { data, error } = await supabase
|
||||
.from('projects')
|
||||
.update(updates)
|
||||
.eq('id', projectId)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error('Error updating project:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data as AethexProject;
|
||||
},
|
||||
|
||||
async deleteProject(projectId: string): Promise<boolean> {
|
||||
const { error } = await supabase
|
||||
.from('projects')
|
||||
.delete()
|
||||
.eq('id', projectId);
|
||||
|
||||
if (error) {
|
||||
console.error('Error deleting project:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
async getAllProjects(limit = 10): Promise<AethexProject[]> {
|
||||
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.error('Error fetching all projects:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data as AethexProject[];
|
||||
},
|
||||
};
|
||||
|
||||
// Achievement Services (maps to existing achievements table)
|
||||
export const aethexAchievementService = {
|
||||
async getAllAchievements(): Promise<AethexAchievement[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('achievements')
|
||||
.select('*')
|
||||
.order('points_reward', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching achievements:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data as AethexAchievement[];
|
||||
},
|
||||
|
||||
async getUserAchievements(userId: string): Promise<AethexAchievement[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('user_achievements')
|
||||
.select(`
|
||||
unlocked_at,
|
||||
achievements (*)
|
||||
`)
|
||||
.eq('user_id', userId)
|
||||
.order('unlocked_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching user achievements:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.map(item => item.achievements).filter(Boolean) as AethexAchievement[];
|
||||
},
|
||||
|
||||
async awardAchievement(userId: string, achievementId: string): Promise<void> {
|
||||
const { error } = await supabase
|
||||
.from('user_achievements')
|
||||
.insert({
|
||||
user_id: userId,
|
||||
achievement_id: achievementId,
|
||||
});
|
||||
|
||||
if (error && error.code !== '23505') { // Ignore duplicate key error
|
||||
console.error('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.icon} ${achievement.name} - ${achievement.description}`,
|
||||
duration: 8000,
|
||||
});
|
||||
|
||||
// Update user's total XP and level
|
||||
await this.updateUserXPAndLevel(userId, achievement.points_reward);
|
||||
}
|
||||
},
|
||||
|
||||
async updateUserXPAndLevel(userId: string, xpGained: number): Promise<void> {
|
||||
// Get current user data
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select('total_xp, level, loyalty_points')
|
||||
.eq('id', userId)
|
||||
.single();
|
||||
|
||||
if (!profile) return;
|
||||
|
||||
const newTotalXP = (profile.total_xp || 0) + xpGained;
|
||||
const newLevel = Math.floor(newTotalXP / 1000) + 1; // 1000 XP per level
|
||||
const newLoyaltyPoints = (profile.loyalty_points || 0) + xpGained;
|
||||
|
||||
// Update profile
|
||||
await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
total_xp: newTotalXP,
|
||||
level: newLevel,
|
||||
loyalty_points: newLoyaltyPoints,
|
||||
})
|
||||
.eq('id', userId);
|
||||
|
||||
// Check for level-up achievements
|
||||
if (newLevel > (profile.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.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async checkAndAwardOnboardingAchievement(userId: string): Promise<void> {
|
||||
const { data: achievement } = await supabase
|
||||
.from('achievements')
|
||||
.select('id')
|
||||
.eq('name', 'AeThex Explorer')
|
||||
.single();
|
||||
|
||||
if (achievement) {
|
||||
await this.awardAchievement(userId, achievement.id);
|
||||
}
|
||||
},
|
||||
|
||||
async checkAndAwardProjectAchievements(userId: string): Promise<void> {
|
||||
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.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.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Notification Service (uses existing notifications table)
|
||||
export const aethexNotificationService = {
|
||||
async getUserNotifications(userId: string): Promise<any[]> {
|
||||
const { data, error } = await supabase
|
||||
.from('notifications')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(10);
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching notifications:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
async markAsRead(notificationId: string): Promise<void> {
|
||||
await supabase
|
||||
.from('notifications')
|
||||
.update({ is_read: true })
|
||||
.eq('id', notificationId);
|
||||
},
|
||||
|
||||
async createNotification(userId: string, type: string, data: any): Promise<void> {
|
||||
await supabase
|
||||
.from('notifications')
|
||||
.insert({
|
||||
user_id: userId,
|
||||
type,
|
||||
data,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// 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();
|
||||
},
|
||||
};
|
||||
Loading…
Reference in a new issue