CodeReview/src/shared/config/database.ts

400 lines
12 KiB
TypeScript
Raw Normal View History

import { createClient } from "@supabase/supabase-js";
import type {
Profile,
Project,
ProjectMember,
AuditTask,
AuditIssue,
InstantAnalysis,
CreateProjectForm,
CreateAuditTaskForm,
InstantAnalysisForm
} from "@/types/types";
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
const isValidUuid = (value?: string): boolean => {
if (!value) return false;
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
};
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
global: {
fetch: undefined
},
auth: {
storageKey: (import.meta.env.VITE_APP_ID || "sb") + "-auth-token"
}
});
// 用户相关API
export const api = {
// Profile相关
async getProfilesById(id: string): Promise<Profile | null> {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', id)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : null;
},
async getProfilesCount(): Promise<number> {
const { count, error } = await supabase
.from('profiles')
.select('*', { count: 'exact', head: true });
if (error) throw error;
return count || 0;
},
async createProfiles(profile: Partial<Profile>): Promise<Profile> {
const { data, error } = await supabase
.from('profiles')
.insert([{
id: profile.id,
phone: profile.phone || null,
email: profile.email || null,
full_name: profile.full_name || null,
avatar_url: profile.avatar_url || null,
role: profile.role || 'member',
github_username: profile.github_username || null,
gitlab_username: profile.gitlab_username || null
}])
.select()
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : {} as Profile;
},
async updateProfile(id: string, updates: Partial<Profile>): Promise<Profile> {
const { data, error } = await supabase
.from('profiles')
.update(updates)
.eq('id', id)
.select()
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : {} as Profile;
},
async getAllProfiles(): Promise<Profile[]> {
const { data, error } = await supabase
.from('profiles')
.select('*')
.order('created_at', { ascending: false });
if (error) throw error;
return Array.isArray(data) ? data : [];
},
// Project相关
async getProjects(): Promise<Project[]> {
const { data, error } = await supabase
.from('projects')
.select(`
*,
owner:profiles!projects_owner_id_fkey(*)
`)
.eq('is_active', true)
.order('created_at', { ascending: false });
if (error) throw error;
return Array.isArray(data) ? data : [];
},
async getProjectById(id: string): Promise<Project | null> {
const { data, error } = await supabase
.from('projects')
.select(`
*,
owner:profiles!projects_owner_id_fkey(*)
`)
.eq('id', id)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : null;
},
async createProject(project: CreateProjectForm & { owner_id?: string }): Promise<Project> {
const { data, error } = await supabase
.from('projects')
.insert([{
name: project.name,
description: project.description || null,
repository_url: project.repository_url || null,
repository_type: project.repository_type || 'other',
default_branch: project.default_branch || 'main',
programming_languages: JSON.stringify(project.programming_languages || []),
owner_id: isValidUuid(project.owner_id) ? project.owner_id : null,
is_active: true
}])
.select(`
*,
owner:profiles!projects_owner_id_fkey(*)
`)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : {} as Project;
},
async updateProject(id: string, updates: Partial<CreateProjectForm>): Promise<Project> {
const updateData: any = { ...updates };
if (updates.programming_languages) {
updateData.programming_languages = JSON.stringify(updates.programming_languages);
}
const { data, error } = await supabase
.from('projects')
.update(updateData)
.eq('id', id)
.select(`
*,
owner:profiles!projects_owner_id_fkey(*)
`)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : {} as Project;
},
async deleteProject(id: string): Promise<void> {
const { error } = await supabase
.from('projects')
.update({ is_active: false })
.eq('id', id);
if (error) throw error;
},
// ProjectMember相关
async getProjectMembers(projectId: string): Promise<ProjectMember[]> {
const { data, error } = await supabase
.from('project_members')
.select(`
*,
user:profiles!project_members_user_id_fkey(*),
project:projects!project_members_project_id_fkey(*)
`)
.eq('project_id', projectId)
.order('joined_at', { ascending: false });
if (error) throw error;
return Array.isArray(data) ? data : [];
},
async addProjectMember(projectId: string, userId: string, role: string = 'member'): Promise<ProjectMember> {
const { data, error } = await supabase
.from('project_members')
.insert([{
project_id: projectId,
user_id: userId,
role: role,
permissions: '{}'
}])
.select(`
*,
user:profiles!project_members_user_id_fkey(*),
project:projects!project_members_project_id_fkey(*)
`)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : {} as ProjectMember;
},
// AuditTask相关
async getAuditTasks(projectId?: string): Promise<AuditTask[]> {
let query = supabase
.from('audit_tasks')
.select(`
*,
project:projects!audit_tasks_project_id_fkey(*),
creator:profiles!audit_tasks_created_by_fkey(*)
`);
if (projectId) {
query = query.eq('project_id', projectId);
}
const { data, error } = await query.order('created_at', { ascending: false });
if (error) throw error;
return Array.isArray(data) ? data : [];
},
async getAuditTaskById(id: string): Promise<AuditTask | null> {
const { data, error } = await supabase
.from('audit_tasks')
.select(`
*,
project:projects!audit_tasks_project_id_fkey(*),
creator:profiles!audit_tasks_created_by_fkey(*)
`)
.eq('id', id)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : null;
},
async createAuditTask(task: CreateAuditTaskForm & { created_by: string }): Promise<AuditTask> {
const { data, error } = await supabase
.from('audit_tasks')
.insert([{
project_id: task.project_id,
task_type: task.task_type,
branch_name: task.branch_name || null,
exclude_patterns: JSON.stringify(task.exclude_patterns || []),
scan_config: JSON.stringify(task.scan_config || {}),
created_by: task.created_by,
status: 'pending'
}])
.select(`
*,
project:projects!audit_tasks_project_id_fkey(*),
creator:profiles!audit_tasks_created_by_fkey(*)
`)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : {} as AuditTask;
},
async updateAuditTask(id: string, updates: Partial<AuditTask>): Promise<AuditTask> {
const { data, error } = await supabase
.from('audit_tasks')
.update(updates)
.eq('id', id)
.select(`
*,
project:projects!audit_tasks_project_id_fkey(*),
creator:profiles!audit_tasks_created_by_fkey(*)
`)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : {} as AuditTask;
},
// AuditIssue相关
async getAuditIssues(taskId: string): Promise<AuditIssue[]> {
const { data, error } = await supabase
.from('audit_issues')
.select(`
*,
task:audit_tasks!audit_issues_task_id_fkey(*),
resolver:profiles!audit_issues_resolved_by_fkey(*)
`)
.eq('task_id', taskId)
.order('severity', { ascending: false })
.order('created_at', { ascending: false });
if (error) throw error;
return Array.isArray(data) ? data : [];
},
async createAuditIssue(issue: Omit<AuditIssue, 'id' | 'created_at' | 'task' | 'resolver'>): Promise<AuditIssue> {
const { data, error } = await supabase
.from('audit_issues')
.insert([issue])
.select(`
*,
task:audit_tasks!audit_issues_task_id_fkey(*),
resolver:profiles!audit_issues_resolved_by_fkey(*)
`)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : {} as AuditIssue;
},
async updateAuditIssue(id: string, updates: Partial<AuditIssue>): Promise<AuditIssue> {
const { data, error } = await supabase
.from('audit_issues')
.update(updates)
.eq('id', id)
.select(`
*,
task:audit_tasks!audit_issues_task_id_fkey(*),
resolver:profiles!audit_issues_resolved_by_fkey(*)
`)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : {} as AuditIssue;
},
// InstantAnalysis相关
async getInstantAnalyses(userId?: string): Promise<InstantAnalysis[]> {
let query = supabase
.from('instant_analyses')
.select(`
*,
user:profiles!instant_analyses_user_id_fkey(*)
`);
if (userId) {
query = query.eq('user_id', userId);
}
const { data, error } = await query.order('created_at', { ascending: false });
if (error) throw error;
return Array.isArray(data) ? data : [];
},
async createInstantAnalysis(analysis: InstantAnalysisForm & {
user_id: string;
analysis_result?: string;
issues_count?: number;
quality_score?: number;
analysis_time?: number;
}): Promise<InstantAnalysis> {
const { data, error } = await supabase
.from('instant_analyses')
.insert([{
user_id: analysis.user_id,
language: analysis.language,
// 遵循安全要求:不持久化用户代码内容
code_content: '',
analysis_result: analysis.analysis_result || '{}',
issues_count: analysis.issues_count || 0,
quality_score: analysis.quality_score || 0,
analysis_time: analysis.analysis_time || 0
}])
.select(`
*,
user:profiles!instant_analyses_user_id_fkey(*)
`)
.limit(1);
if (error) throw error;
return Array.isArray(data) && data.length > 0 ? data[0] : {} as InstantAnalysis;
},
// 统计相关
async getProjectStats(): Promise<any> {
const [projectsResult, tasksResult, issuesResult] = await Promise.all([
supabase.from('projects').select('id, is_active', { count: 'exact' }),
supabase.from('audit_tasks').select('id, status', { count: 'exact' }),
supabase.from('audit_issues').select('id, status', { count: 'exact' })
]);
return {
total_projects: projectsResult.count || 0,
active_projects: projectsResult.data?.filter(p => p.is_active).length || 0,
total_tasks: tasksResult.count || 0,
completed_tasks: tasksResult.data?.filter(t => t.status === 'completed').length || 0,
total_issues: issuesResult.count || 0,
resolved_issues: issuesResult.data?.filter(i => i.status === 'resolved').length || 0
};
}
};