From adbff8ded2a90e264e89be79352de51e4436337c Mon Sep 17 00:00:00 2001 From: Arjuna Date: Sat, 7 Feb 2026 20:41:15 +0700 Subject: [PATCH] feat: backend table DDL logic added --- Internal/adapters/repository/dynamicDBRepo.go | 17 ++++++ Internal/api/Routes/routev1.go | 14 +++-- Internal/api/handlers/dynamicDBHandler.go | 48 +++++++++++++++ Internal/core/services/dynamicDBService.go | 59 +++++++++++++++++++ cmd/server/main.go | 17 ++++-- 5 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 Internal/adapters/repository/dynamicDBRepo.go create mode 100644 Internal/api/handlers/dynamicDBHandler.go create mode 100644 Internal/core/services/dynamicDBService.go diff --git a/Internal/adapters/repository/dynamicDBRepo.go b/Internal/adapters/repository/dynamicDBRepo.go new file mode 100644 index 0000000..d7ae243 --- /dev/null +++ b/Internal/adapters/repository/dynamicDBRepo.go @@ -0,0 +1,17 @@ +package repository + +type DynamicDBRepo struct { + DB *DBContainer +} + +func NewDynamicDB(db *DBContainer) *DynamicDBRepo { + return &DynamicDBRepo{DB: db} +} + +func (dr *DynamicDBRepo) CreateDynamicDB(query string) error { + _, err := dr.DB.Sqlx.Exec(query) + if err != nil { + return err + } + return nil +} diff --git a/Internal/api/Routes/routev1.go b/Internal/api/Routes/routev1.go index 03336d5..7db3f07 100644 --- a/Internal/api/Routes/routev1.go +++ b/Internal/api/Routes/routev1.go @@ -9,12 +9,13 @@ import ( ) type Deps struct { - User *handlers.UserHandler - Auth *handlers.AuthHandler - System *handlers.SystemHandler - Project *handlers.ProjectHandler - UserRepo *repository.UserRepository - Config *config.Config + User *handlers.UserHandler + Auth *handlers.AuthHandler + System *handlers.SystemHandler + Project *handlers.ProjectHandler + DynamicDB *handlers.DynamicDBHandler + UserRepo *repository.UserRepository + Config *config.Config } func SetupRouterV1(r *gin.Engine, deps Deps) { @@ -57,6 +58,7 @@ 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) } } } diff --git a/Internal/api/handlers/dynamicDBHandler.go b/Internal/api/handlers/dynamicDBHandler.go new file mode 100644 index 0000000..42db287 --- /dev/null +++ b/Internal/api/handlers/dynamicDBHandler.go @@ -0,0 +1,48 @@ +package handlers + +import ( + "strconv" + + "github.com/Arjuna-Ragil/Localbase/Internal/core/services" + "github.com/gin-gonic/gin" +) + +type DynamicDBHandler struct { + Serv *services.DynamicDBService +} + +func NewDynamicDBHandler(serv *services.DynamicDBService) *DynamicDBHandler { + return &DynamicDBHandler{Serv: serv} +} + +func (dh *DynamicDBHandler) CreateDynamicDBHandler(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 + } + var input services.CreateTableReq + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(400, gin.H{ + "message": "Invalid json body", + "error": err.Error(), + }) + return + } + if err := dh.Serv.CreateDynamicTable(uint(projectID), input); nil != err { + c.JSON(500, gin.H{ + "message": "Failed to create dynamic table", + "error": err.Error(), + }) + return + } + c.JSON(200, gin.H{ + "message": "Successfully created dynamic table", + "data": nil, + }) + +} diff --git a/Internal/core/services/dynamicDBService.go b/Internal/core/services/dynamicDBService.go new file mode 100644 index 0000000..364301c --- /dev/null +++ b/Internal/core/services/dynamicDBService.go @@ -0,0 +1,59 @@ +package services + +import ( + "fmt" + "regexp" + "strings" + + "github.com/Arjuna-Ragil/Localbase/Internal/adapters/repository" +) + +type DynamicDBService struct { + Repo *repository.DynamicDBRepo +} + +func NewDynamicDBService(repo *repository.DynamicDBRepo) *DynamicDBService { + return &DynamicDBService{Repo: repo} +} + +type ColumnReq struct { + Name string `json:"name"` + Type string `json:"type"` +} + +type CreateTableReq struct { + TableName string `json:"table_name"` + Columns []ColumnReq `json:"columns"` +} + +func sanitizeInput(input string) string { + result := strings.ToLower(input) + result = strings.ReplaceAll(result, " ", "_") + reg := regexp.MustCompile("[^a-z0-9_]+") + result = reg.ReplaceAllString(result, "") + return result +} + +func (ds *DynamicDBService) CreateDynamicTable(projectID uint, req CreateTableReq) error { + safeTableName := fmt.Sprintf("proj_%d_%s", projectID, sanitizeInput(req.TableName)) + + var queryBuilder strings.Builder + queryBuilder.WriteString(fmt.Sprintf("CREATE TABLE %s (", safeTableName)) + queryBuilder.WriteString("id SERIAL PRIMARY KEY, ") + queryBuilder.WriteString("created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ") + + for _, col := range req.Columns { + safeColName := sanitizeInput(col.Name) + queryBuilder.WriteString(fmt.Sprintf("%s %s, ", safeColName, col.Type)) + } + + query := queryBuilder.String() + query = strings.TrimSuffix(query, ", ") + query += ");" + + fmt.Println(query) + if err := ds.Repo.CreateDynamicDB(query); err != nil { + return err + } + return nil +} diff --git a/cmd/server/main.go b/cmd/server/main.go index 3ceb2f3..b6393a4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -64,12 +64,17 @@ func SetupApp(db *repository.DBContainer, cfg *config.Config) Routes.Deps { projectService := services.NewProjectService(projectRepo, userRepo) projectHandler := handlers.NewProjectHandler(projectService) + dynamicDBRepo := repository.NewDynamicDB(db) + dynamicDBService := services.NewDynamicDBService(dynamicDBRepo) + dynamicDBHandler := handlers.NewDynamicDBHandler(dynamicDBService) + return Routes.Deps{ - User: userHandler, - Auth: authHandler, - System: systemHandler, - Project: projectHandler, - UserRepo: userRepo, - Config: cfg, + User: userHandler, + Auth: authHandler, + System: systemHandler, + Project: projectHandler, + DynamicDB: dynamicDBHandler, + UserRepo: userRepo, + Config: cfg, } }