Add cascade delete for projects with Gitea repository cleanup
- Add deleteGiteaRepo server action to remove repos via Gitea API - Add deleteProject function with full cascade: - Deletes Gitea repository if linked - Removes all agent_runs, messages, backlog items - Removes project phases, activities, recommendations - Finally removes the project itself - Add ProjectSettingsMenu with delete confirmation dialog - Add use-toast hook for notifications using sonner - Add shadcn alert-dialog component - Restore brand button variant after shadcn update 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import { deleteGiteaRepo } from '@/app/(dashboard)/projects/new/actions'
|
||||
import type {
|
||||
DesignPhase,
|
||||
PhaseStatus,
|
||||
@@ -317,3 +318,113 @@ export async function getProjectActivities(projectId: string, limit = 20) {
|
||||
created_at: string
|
||||
}>
|
||||
}
|
||||
|
||||
// Delete project with cascade (includes Gitea repo deletion)
|
||||
export async function deleteProject(projectId: string): Promise<{ success: boolean; error?: string }> {
|
||||
const supabase = createClient()
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
|
||||
if (!user) {
|
||||
return { success: false, error: 'Not authenticated' }
|
||||
}
|
||||
|
||||
try {
|
||||
// First, get the project to check ownership and get gitea_repo
|
||||
const { data: projectData, error: fetchError } = await supabase
|
||||
.from('projects')
|
||||
.select('id, user_id, gitea_repo, name')
|
||||
.eq('id', projectId)
|
||||
.single()
|
||||
|
||||
if (fetchError || !projectData) {
|
||||
return { success: false, error: 'Project not found' }
|
||||
}
|
||||
|
||||
const project = projectData as unknown as {
|
||||
id: string
|
||||
user_id: string
|
||||
gitea_repo: string | null
|
||||
name: string
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
if (project.user_id !== user.id) {
|
||||
return { success: false, error: 'Not authorized to delete this project' }
|
||||
}
|
||||
|
||||
// Extract repo name from gitea_repo URL if it exists
|
||||
// Format: https://gitea.mylder.io/admin/repo-name
|
||||
if (project.gitea_repo) {
|
||||
const repoMatch = project.gitea_repo.match(/\/([^/]+)$/)
|
||||
const repoName = repoMatch ? repoMatch[1] : null
|
||||
|
||||
if (repoName) {
|
||||
const giteaResult = await deleteGiteaRepo(repoName)
|
||||
if (!giteaResult.success) {
|
||||
console.error('Failed to delete Gitea repo:', giteaResult.error)
|
||||
// Continue with database deletion even if Gitea fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete related data in order (due to foreign key constraints)
|
||||
// Delete agent_runs
|
||||
await supabase
|
||||
.from('agent_runs')
|
||||
.delete()
|
||||
.eq('project_id', projectId)
|
||||
|
||||
// Delete messages
|
||||
await supabase
|
||||
.from('messages')
|
||||
.delete()
|
||||
.eq('project_id', projectId)
|
||||
|
||||
// Delete backlog items
|
||||
await supabase
|
||||
.from('backlog_items')
|
||||
.delete()
|
||||
.eq('project_id', projectId)
|
||||
|
||||
// Delete project activities
|
||||
await supabase
|
||||
.from('project_activities')
|
||||
.delete()
|
||||
.eq('project_id', projectId)
|
||||
|
||||
// Delete AI recommendations
|
||||
await supabase
|
||||
.from('ai_recommendations')
|
||||
.delete()
|
||||
.eq('project_id', projectId)
|
||||
|
||||
// Delete project phases
|
||||
await supabase
|
||||
.from('project_phases')
|
||||
.delete()
|
||||
.eq('project_id', projectId)
|
||||
|
||||
// Delete health snapshots
|
||||
await supabase
|
||||
.from('project_health_snapshots')
|
||||
.delete()
|
||||
.eq('project_id', projectId)
|
||||
|
||||
// Finally, delete the project itself
|
||||
const { error: deleteError } = await supabase
|
||||
.from('projects')
|
||||
.delete()
|
||||
.eq('id', projectId)
|
||||
|
||||
if (deleteError) {
|
||||
return { success: false, error: deleteError.message }
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to delete project'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user