diff --git a/Internal/adapters/repository/dynamicDBRepo.go b/Internal/adapters/repository/dynamicDBRepo.go index 36f0081..7f082f9 100644 --- a/Internal/adapters/repository/dynamicDBRepo.go +++ b/Internal/adapters/repository/dynamicDBRepo.go @@ -43,3 +43,11 @@ func (dr *DynamicDBRepo) GetTables(projectID uint) ([]domain.DynTableDef, error) } return tables, nil } + +func (dr *DynamicDBRepo) DropDynTable(tx *gorm.DB, projectID uint, tableID uint) error { + var dynTable domain.DynTableDef + if err := tx.Where("project_id = ? AND id = ?", projectID, tableID).Delete(&dynTable).Error; err != nil { + return err + } + return nil +} diff --git a/Internal/api/Routes/routev1.go b/Internal/api/Routes/routev1.go index d1e070f..cdca18c 100644 --- a/Internal/api/Routes/routev1.go +++ b/Internal/api/Routes/routev1.go @@ -59,8 +59,12 @@ func SetupRouterV1(r *gin.Engine, deps Deps) { { manage.POST("/invite/:projectid", deps.Project.InviteProjectHandler) manage.DELETE("/remove/:projuserid", deps.Project.RemoveProjectUserHandler) - manage.POST("/create/table/:projectid", deps.DynamicDB.CreateDynamicDBHandler) - manage.POST("/create/col/:projectid/:tableid", deps.DynamicDB.AddDynamicColHandler) + table := manage.Group("/table") + { + table.POST("/create/:projectid", deps.DynamicDB.CreateDynamicDBHandler) + table.POST("/create/col/:projectid/:tableid", deps.DynamicDB.AddDynamicColHandler) + table.DELETE("/drop/:projectid/:tableid", deps.DynamicDB.DropDynTableHandler) + } } } } diff --git a/Internal/api/handlers/dynamicDBHandler.go b/Internal/api/handlers/dynamicDBHandler.go index 493999a..6d57a6c 100644 --- a/Internal/api/handlers/dynamicDBHandler.go +++ b/Internal/api/handlers/dynamicDBHandler.go @@ -117,3 +117,46 @@ func (dh *DynamicDBHandler) GetDynTablesHandler(c *gin.Context) { "data": tables, }) } + +func (dh *DynamicDBHandler) DropDynTableHandler(c *gin.Context) { + projectIDStr := c.Param("projectid") + projectID, err := strconv.Atoi(projectIDStr) + if err != nil { + c.JSON(400, gin.H{ + "message": "Project ID not valid", + "error": err.Error(), + }) + return + } + tableIDStr := c.Param("tableid") + tableID, err := strconv.Atoi(tableIDStr) + if err != nil { + c.JSON(400, gin.H{ + "message": "Table ID not valid", + "data": err.Error(), + }) + return + } + input := services.DropTableReq{ + ProjectID: uint(projectID), + TableID: uint(tableID), + } + if err = c.ShouldBindJSON(&input); err != nil { + c.JSON(400, gin.H{ + "message": "Invalid input", + "error": err.Error(), + }) + return + } + if err = dh.Serv.DelDynTable(&input); err != nil { + c.JSON(500, gin.H{ + "message": "Failed to delete dynamic table", + "error": err.Error(), + }) + return + } + c.JSON(200, gin.H{ + "message": "Successfully deleted dynamic table", + "data": nil, + }) +} diff --git a/Internal/core/domain/projectdb.go b/Internal/core/domain/projectdb.go index 081e42d..cf66d81 100644 --- a/Internal/core/domain/projectdb.go +++ b/Internal/core/domain/projectdb.go @@ -26,7 +26,7 @@ type DynTableDef struct { ProjectID uint `gorm:"not null" json:"project_id"` Name string `gorm:"size:255;not null" json:"name"` Alias string `gorm:"size:255;not null" json:"alias"` - Columns []DynColDef `gorm:"foreignKey:DynTableDefID" json:"columns"` + Columns []DynColDef `gorm:"foreignKey:DynTableDefID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"columns"` } type DynColDef struct { diff --git a/Internal/core/services/dynamicDBService.go b/Internal/core/services/dynamicDBService.go index f0f8166..bb9baaf 100644 --- a/Internal/core/services/dynamicDBService.go +++ b/Internal/core/services/dynamicDBService.go @@ -28,6 +28,12 @@ type DDLTableReq struct { Columns []ColumnReq `json:"columns"` } +type DropTableReq struct { + ProjectID uint `json:"project_id"` + TableID uint `json:"id"` + TableName string `json:"table_name"` +} + func sanitizeInput(input string) string { result := strings.ToLower(input) result = strings.ReplaceAll(result, " ", "_") @@ -126,3 +132,30 @@ func (ds *DynamicDBService) GetDynamicTables(projectID uint) ([]domain.DynTableD } return tables, nil } + +// DelDynTable DROP TABLE IF EXISTS name CASCADE; +func (ds *DynamicDBService) DelDynTable(input *DropTableReq) error { + tx := ds.DB.Gorm.Begin() + + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + safeTableName := fmt.Sprintf("proj_%d_%s", input.ProjectID, sanitizeInput(input.TableName)) + + if err := ds.Repo.DropDynTable(tx, input.ProjectID, input.TableID); err != nil { + tx.Rollback() + return err + } + + var queryBuilder strings.Builder + queryBuilder.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE", safeTableName)) + query := queryBuilder.String() + + if err := ds.Repo.RunDynamicQuery(tx, query); err != nil { + return err + } + return tx.Commit().Error +} diff --git a/Lb-web/src/features/project/pages/ProjectDatabase.tsx b/Lb-web/src/features/project/pages/ProjectDatabase.tsx index b13b072..98a79a2 100644 --- a/Lb-web/src/features/project/pages/ProjectDatabase.tsx +++ b/Lb-web/src/features/project/pages/ProjectDatabase.tsx @@ -50,6 +50,11 @@ export default function ProjectDatabase() { // Expand/Collapse State const [expandedTables, setExpandedTables] = useState>({}); + // Delete Table State + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [tableToDelete, setTableToDelete] = useState(null); + const [deleteLoading, setDeleteLoading] = useState(false); + useEffect(() => { if (projectId) { @@ -180,6 +185,27 @@ export default function ProjectDatabase() { }; + const openDeleteDialog = (table: DynTableDef) => { + setTableToDelete(table); + setDeleteDialogOpen(true); + }; + + const confirmDeleteTable = async () => { + if (!tableToDelete) return; + setDeleteLoading(true); + try { + await dynamicDBService.deleteTable(projectId, tableToDelete.id); + setDeleteDialogOpen(false); + setTableToDelete(null); + fetchTables(); + } catch (err) { + console.error("Failed to delete table", err); + // In a real app, show a toast here + } finally { + setDeleteLoading(false); + } + }; + if (loadingProject) { return
; } @@ -383,6 +409,42 @@ export default function ProjectDatabase() { + + {/* Delete Confirmation Dialog */} + + + + + Delete Table + + + Are you sure you want to delete the table {tableToDelete?.alias}? +

+ This action cannot be undone and will permanently delete all data in this table. +
+
+ + + + +
+
{/* Tables List */} @@ -408,6 +470,18 @@ export default function ProjectDatabase() {
+