Skip to content

Commit a2f37eb

Browse files
authored
Merge pull request #54 from Arjuna-Ragil/feat/52-drop-ddl
feat: drop table ddl added
2 parents 64982e3 + 557737d commit a2f37eb

7 files changed

Lines changed: 177 additions & 5 deletions

File tree

Internal/adapters/repository/dynamicDBRepo.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,11 @@ func (dr *DynamicDBRepo) GetTables(projectID uint) ([]domain.DynTableDef, error)
4343
}
4444
return tables, nil
4545
}
46+
47+
func (dr *DynamicDBRepo) DropDynTable(tx *gorm.DB, projectID uint, tableID uint) error {
48+
var dynTable domain.DynTableDef
49+
if err := tx.Where("project_id = ? AND id = ?", projectID, tableID).Delete(&dynTable).Error; err != nil {
50+
return err
51+
}
52+
return nil
53+
}

Internal/api/Routes/routev1.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,12 @@ func SetupRouterV1(r *gin.Engine, deps Deps) {
5959
{
6060
manage.POST("/invite/:projectid", deps.Project.InviteProjectHandler)
6161
manage.DELETE("/remove/:projuserid", deps.Project.RemoveProjectUserHandler)
62-
manage.POST("/create/table/:projectid", deps.DynamicDB.CreateDynamicDBHandler)
63-
manage.POST("/create/col/:projectid/:tableid", deps.DynamicDB.AddDynamicColHandler)
62+
table := manage.Group("/table")
63+
{
64+
table.POST("/create/:projectid", deps.DynamicDB.CreateDynamicDBHandler)
65+
table.POST("/create/col/:projectid/:tableid", deps.DynamicDB.AddDynamicColHandler)
66+
table.DELETE("/drop/:projectid/:tableid", deps.DynamicDB.DropDynTableHandler)
67+
}
6468
}
6569
}
6670
}

Internal/api/handlers/dynamicDBHandler.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,46 @@ func (dh *DynamicDBHandler) GetDynTablesHandler(c *gin.Context) {
117117
"data": tables,
118118
})
119119
}
120+
121+
func (dh *DynamicDBHandler) DropDynTableHandler(c *gin.Context) {
122+
projectIDStr := c.Param("projectid")
123+
projectID, err := strconv.Atoi(projectIDStr)
124+
if err != nil {
125+
c.JSON(400, gin.H{
126+
"message": "Project ID not valid",
127+
"error": err.Error(),
128+
})
129+
return
130+
}
131+
tableIDStr := c.Param("tableid")
132+
tableID, err := strconv.Atoi(tableIDStr)
133+
if err != nil {
134+
c.JSON(400, gin.H{
135+
"message": "Table ID not valid",
136+
"data": err.Error(),
137+
})
138+
return
139+
}
140+
input := services.DropTableReq{
141+
ProjectID: uint(projectID),
142+
TableID: uint(tableID),
143+
}
144+
if err = c.ShouldBindJSON(&input); err != nil {
145+
c.JSON(400, gin.H{
146+
"message": "Invalid input",
147+
"error": err.Error(),
148+
})
149+
return
150+
}
151+
if err = dh.Serv.DelDynTable(&input); err != nil {
152+
c.JSON(500, gin.H{
153+
"message": "Failed to delete dynamic table",
154+
"error": err.Error(),
155+
})
156+
return
157+
}
158+
c.JSON(200, gin.H{
159+
"message": "Successfully deleted dynamic table",
160+
"data": nil,
161+
})
162+
}

Internal/core/domain/projectdb.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type DynTableDef struct {
2626
ProjectID uint `gorm:"not null" json:"project_id"`
2727
Name string `gorm:"size:255;not null" json:"name"`
2828
Alias string `gorm:"size:255;not null" json:"alias"`
29-
Columns []DynColDef `gorm:"foreignKey:DynTableDefID" json:"columns"`
29+
Columns []DynColDef `gorm:"foreignKey:DynTableDefID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"columns"`
3030
}
3131

3232
type DynColDef struct {

Internal/core/services/dynamicDBService.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ type DDLTableReq struct {
2828
Columns []ColumnReq `json:"columns"`
2929
}
3030

31+
type DropTableReq struct {
32+
ProjectID uint `json:"project_id"`
33+
TableID uint `json:"id"`
34+
TableName string `json:"table_name"`
35+
}
36+
3137
func sanitizeInput(input string) string {
3238
result := strings.ToLower(input)
3339
result = strings.ReplaceAll(result, " ", "_")
@@ -126,3 +132,30 @@ func (ds *DynamicDBService) GetDynamicTables(projectID uint) ([]domain.DynTableD
126132
}
127133
return tables, nil
128134
}
135+
136+
// DelDynTable DROP TABLE IF EXISTS name CASCADE;
137+
func (ds *DynamicDBService) DelDynTable(input *DropTableReq) error {
138+
tx := ds.DB.Gorm.Begin()
139+
140+
defer func() {
141+
if r := recover(); r != nil {
142+
tx.Rollback()
143+
}
144+
}()
145+
146+
safeTableName := fmt.Sprintf("proj_%d_%s", input.ProjectID, sanitizeInput(input.TableName))
147+
148+
if err := ds.Repo.DropDynTable(tx, input.ProjectID, input.TableID); err != nil {
149+
tx.Rollback()
150+
return err
151+
}
152+
153+
var queryBuilder strings.Builder
154+
queryBuilder.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE", safeTableName))
155+
query := queryBuilder.String()
156+
157+
if err := ds.Repo.RunDynamicQuery(tx, query); err != nil {
158+
return err
159+
}
160+
return tx.Commit().Error
161+
}

Lb-web/src/features/project/pages/ProjectDatabase.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ export default function ProjectDatabase() {
5050
// Expand/Collapse State
5151
const [expandedTables, setExpandedTables] = useState<Record<number, boolean>>({});
5252

53+
// Delete Table State
54+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
55+
const [tableToDelete, setTableToDelete] = useState<DynTableDef | null>(null);
56+
const [deleteLoading, setDeleteLoading] = useState(false);
57+
5358

5459
useEffect(() => {
5560
if (projectId) {
@@ -180,6 +185,27 @@ export default function ProjectDatabase() {
180185
};
181186

182187

188+
const openDeleteDialog = (table: DynTableDef) => {
189+
setTableToDelete(table);
190+
setDeleteDialogOpen(true);
191+
};
192+
193+
const confirmDeleteTable = async () => {
194+
if (!tableToDelete) return;
195+
setDeleteLoading(true);
196+
try {
197+
await dynamicDBService.deleteTable(projectId, tableToDelete.id);
198+
setDeleteDialogOpen(false);
199+
setTableToDelete(null);
200+
fetchTables();
201+
} catch (err) {
202+
console.error("Failed to delete table", err);
203+
// In a real app, show a toast here
204+
} finally {
205+
setDeleteLoading(false);
206+
}
207+
};
208+
183209
if (loadingProject) {
184210
return <div className="flex h-screen items-center justify-center bg-slate-50"><Loader2 className="animate-spin text-sky-600" /></div>;
185211
}
@@ -383,6 +409,42 @@ export default function ProjectDatabase() {
383409
</DialogFooter>
384410
</DialogContent>
385411
</Dialog>
412+
413+
{/* Delete Confirmation Dialog */}
414+
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
415+
<DialogContent>
416+
<DialogHeader>
417+
<DialogTitle className="text-red-600 flex items-center gap-2">
418+
<Trash2 className="h-5 w-5" /> Delete Table
419+
</DialogTitle>
420+
<DialogDescription>
421+
Are you sure you want to delete the table <strong>{tableToDelete?.alias}</strong>?
422+
<br /><br />
423+
This action cannot be undone and will permanently delete all data in this table.
424+
</DialogDescription>
425+
</DialogHeader>
426+
<DialogFooter>
427+
<Button variant="outline" onClick={() => setDeleteDialogOpen(false)} disabled={deleteLoading}>
428+
Cancel
429+
</Button>
430+
<Button
431+
variant="destructive"
432+
onClick={confirmDeleteTable}
433+
disabled={deleteLoading}
434+
className="bg-red-600 hover:bg-red-700"
435+
>
436+
{deleteLoading ? (
437+
<>
438+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
439+
Deleting...
440+
</>
441+
) : (
442+
"Delete Table"
443+
)}
444+
</Button>
445+
</DialogFooter>
446+
</DialogContent>
447+
</Dialog>
386448
</div>
387449

388450
{/* Tables List */}
@@ -408,6 +470,18 @@ export default function ProjectDatabase() {
408470
</div>
409471
</div>
410472
<div className="flex items-center space-x-2">
473+
<Button
474+
size="icon"
475+
variant="ghost"
476+
className="h-8 w-8 text-red-500 hover:text-red-700 hover:bg-red-100"
477+
onClick={(e) => {
478+
e.stopPropagation();
479+
openDeleteDialog(t);
480+
}}
481+
title="Drop Table"
482+
>
483+
<Trash2 className="h-4 w-4" />
484+
</Button>
411485
<Button size="sm" variant="ghost" className="text-sky-600" onClick={(e) => {
412486
e.stopPropagation();
413487
openAddColDialog(t);

Lb-web/src/features/project/services/dynamicDBService.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,28 @@ export interface DDLTableReq {
1212

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

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

2424
getTables: async (projectId: number): Promise<DynTableDef[]> => {
2525
const response = await api.get(`/protected/project/${projectId}/tables`);
2626
return response.data.data;
27+
},
28+
29+
deleteTable: async (projectId: number, tableId: number) => {
30+
const response = await api.delete(`/protected/project/manage/table/drop/${projectId}/${tableId}`, {
31+
data: {
32+
project_id: projectId,
33+
table_id: tableId
34+
}
35+
});
36+
return response.data;
2737
}
2838
};
2939

0 commit comments

Comments
 (0)