Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Internal/adapters/repository/dynamicDBRepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
8 changes: 6 additions & 2 deletions Internal/api/Routes/routev1.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
Expand Down
43 changes: 43 additions & 0 deletions Internal/api/handlers/dynamicDBHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}
2 changes: 1 addition & 1 deletion Internal/core/domain/projectdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
33 changes: 33 additions & 0 deletions Internal/core/services/dynamicDBService.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, " ", "_")
Expand Down Expand Up @@ -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
}
74 changes: 74 additions & 0 deletions Lb-web/src/features/project/pages/ProjectDatabase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,18 @@
// Expand/Collapse State
const [expandedTables, setExpandedTables] = useState<Record<number, boolean>>({});

// Delete Table State
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [tableToDelete, setTableToDelete] = useState<DynTableDef | null>(null);
const [deleteLoading, setDeleteLoading] = useState(false);


useEffect(() => {
if (projectId) {
fetchProject();
fetchTables();
}
}, [projectId, id]);

Check warning on line 64 in Lb-web/src/features/project/pages/ProjectDatabase.tsx

View workflow job for this annotation

GitHub Actions / Frontend integrity checks

React Hook useEffect has missing dependencies: 'fetchProject' and 'fetchTables'. Either include them or remove the dependency array

const fetchProject = async () => {
try {
Expand Down Expand Up @@ -121,7 +126,7 @@
setTableName("");
setNewColumns([]);
fetchTables(); // Refresh list
} catch (err: any) {

Check failure on line 129 in Lb-web/src/features/project/pages/ProjectDatabase.tsx

View workflow job for this annotation

GitHub Actions / Frontend integrity checks

Unexpected any. Specify a different type
console.error("Failed to create table", err);
setCreateError(err.response?.data?.message || err.message || "Failed to create table");
} finally {
Expand Down Expand Up @@ -180,6 +185,27 @@
};


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 <div className="flex h-screen items-center justify-center bg-slate-50"><Loader2 className="animate-spin text-sky-600" /></div>;
}
Expand Down Expand Up @@ -383,6 +409,42 @@
</DialogFooter>
</DialogContent>
</Dialog>

{/* Delete Confirmation Dialog */}
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-red-600 flex items-center gap-2">
<Trash2 className="h-5 w-5" /> Delete Table
</DialogTitle>
<DialogDescription>
Are you sure you want to delete the table <strong>{tableToDelete?.alias}</strong>?
<br /><br />
This action cannot be undone and will permanently delete all data in this table.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setDeleteDialogOpen(false)} disabled={deleteLoading}>
Cancel
</Button>
<Button
variant="destructive"
onClick={confirmDeleteTable}
disabled={deleteLoading}
className="bg-red-600 hover:bg-red-700"
>
{deleteLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Deleting...
</>
) : (
"Delete Table"
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>

{/* Tables List */}
Expand All @@ -408,6 +470,18 @@
</div>
</div>
<div className="flex items-center space-x-2">
<Button
size="icon"
variant="ghost"
className="h-8 w-8 text-red-500 hover:text-red-700 hover:bg-red-100"
onClick={(e) => {
e.stopPropagation();
openDeleteDialog(t);
}}
title="Drop Table"
>
<Trash2 className="h-4 w-4" />
</Button>
<Button size="sm" variant="ghost" className="text-sky-600" onClick={(e) => {
e.stopPropagation();
openAddColDialog(t);
Expand Down
14 changes: 12 additions & 2 deletions Lb-web/src/features/project/services/dynamicDBService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,28 @@ export interface DDLTableReq {

export const dynamicDBService = {
createTable: async (projectId: number, data: DDLTableReq) => {
const response = await api.post(`/protected/project/manage/create/table/${projectId}`, data);
const response = await api.post(`/protected/project/manage/table/create/${projectId}`, data);
return response.data;
},

addColumn: async (projectId: number, tableId: number, data: DDLTableReq) => {
const response = await api.post(`/protected/project/manage/create/col/${projectId}/${tableId}`, data);
const response = await api.post(`/protected/project/manage/table/create/col/${projectId}/${tableId}`, data);
return response.data;
},

getTables: async (projectId: number): Promise<DynTableDef[]> => {
const response = await api.get(`/protected/project/${projectId}/tables`);
return response.data.data;
},

deleteTable: async (projectId: number, tableId: number) => {
const response = await api.delete(`/protected/project/manage/table/drop/${projectId}/${tableId}`, {
data: {
project_id: projectId,
table_id: tableId
}
});
return response.data;
}
};

Expand Down
Loading