diff --git a/Makefile b/Makefile index 3bd59de..9b7e148 100644 --- a/Makefile +++ b/Makefile @@ -82,3 +82,4 @@ generate-mocks: @mockery --name=ProjectRepo --dir=cmd/repository --output=cmd/repository --outpkg=repository --filename=project_mock.go --structname=MockProjectRepo # Helpers @mockery --name=ProjectHelper --dir=cmd/helpers --output=cmd/helpers --outpkg=helpers --filename=projectHelper_mock.go --structname=MockProjectHelper + @mockery --name=CodeHelper --dir=cmd/helpers --output=cmd/helpers --outpkg=helpers --filename=codeHelper_mock.go --structname=MockCodeHelper diff --git a/cmd/app.go b/cmd/app.go index dbc865e..43a1084 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -1,72 +1,19 @@ package cmd import ( - "fmt" - "github.com/tech-thinker/gozen/cmd/service" - "github.com/tech-thinker/gozen/models" "github.com/urfave/cli/v2" ) type App interface { CreateProject() *cli.Command + GenerateModel() *cli.Command } type app struct { appService service.AppService } -func (c *app) CreateProject() *cli.Command { - - var packageName, outputDir, driver string - - return &cli.Command{ - Name: "create", - Usage: "Create new Projects.", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "pkg", - Aliases: []string{"p"}, - Value: "", - Usage: "Package name for new project.", - Destination: &packageName, - }, - &cli.StringFlag{ - Name: "output", - Aliases: []string{"o"}, - Value: ".", - Usage: "Output directory for new project.", - Destination: &outputDir, - }, - &cli.StringFlag{ - Name: "driver", - Aliases: []string{"d"}, - Value: "sqlite", - Usage: "Database driver for new project. eg. [sqlite, mysql, postgres]", - Destination: &driver, - }, - }, - Action: func(ctx *cli.Context) error { - project := models.Project{ - AppName: ctx.Args().Get(0), - PackageName: packageName, - Driver: driver, - WorkingDir: outputDir, - } - - err := project.Validate() - if err != nil { - fmt.Println(err) - return nil - } - - project.AutoFixes() - - return c.appService.CreateApp(project) - }, - } -} - func NewApp( appService service.AppService, ) App { diff --git a/cmd/generate.go b/cmd/generate.go new file mode 100644 index 0000000..e4a2974 --- /dev/null +++ b/cmd/generate.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "strings" + + "github.com/tech-thinker/gozen/models" + "github.com/tech-thinker/gozen/utils" + "github.com/urfave/cli/v2" +) + +func (c *app) GenerateModel() *cli.Command { + return &cli.Command{ + Name: "generate", + Aliases: []string{"g"}, + Usage: "Code generator.", + Action: func(ctx *cli.Context) error { + + var project models.Project + project.LoadFromJsonFile() + + // project := models.Project{ + // AppName: ctx.Args().Get(0), + // PackageName: "github.com/mrasif/demo1", + // WorkingDir: "./", + // } + + // project.AutoFixes() + + generateType := ctx.Args().Get(0) + name := ctx.Args().Get(1) + + interfaceName, structName := utils.TransformString(name) + route := strings.ToLower(structName) + + generator := models.Generator{ + Project: project, + Type: generateType, + InterfaceName: interfaceName, + StructName: structName, + RouteName: route, + } + + return c.appService.GenerateModel(generator) + }, + } +} diff --git a/cmd/helpers/codeHelper.go b/cmd/helpers/codeHelper.go new file mode 100644 index 0000000..731f095 --- /dev/null +++ b/cmd/helpers/codeHelper.go @@ -0,0 +1,43 @@ +package helpers + +import ( + "fmt" + + "github.com/tech-thinker/gozen/cmd/repository" + "github.com/tech-thinker/gozen/models" +) + +type CodeHelper interface { + GenerateModel(doc models.Generator) error +} + +type codeHelper struct { + systemRepo repository.SystemRepo +} + +func (h *codeHelper) GenerateModel(doc models.Generator) error { + + modelPath := fmt.Sprintf(`/models/%s.go`, doc.StructName) + repoPath := fmt.Sprintf(`/repository/%s.go`, doc.StructName) + svcPath := fmt.Sprintf(`/service/%s.go`, doc.StructName) + ctrlPath := fmt.Sprintf(`/app/rest/controllers/%s.go`, doc.StructName) + + fileConfigs := []models.FileConfig{ + {TemplatePath: "templates/models/standard.tmpl", Destination: modelPath}, + {TemplatePath: "templates/repository/standard.tmpl", Destination: repoPath}, + {TemplatePath: "templates/service/standard.tmpl", Destination: svcPath}, + {TemplatePath: "templates/app/rest/controllers/standard.tmpl", Destination: ctrlPath}, + } + + fmt.Println("Need to add New Service to Service registry.") + + return h.systemRepo.WriteAll(doc.Project.GetCWD(), fileConfigs, doc) +} + +func NewCodeHelper( + systemRepo repository.SystemRepo, +) CodeHelper { + return &codeHelper{ + systemRepo: systemRepo, + } +} diff --git a/cmd/helpers/codeHelper_mock.go b/cmd/helpers/codeHelper_mock.go new file mode 100644 index 0000000..2e8c7f9 --- /dev/null +++ b/cmd/helpers/codeHelper_mock.go @@ -0,0 +1,45 @@ +// Code generated by mockery v2.51.1. DO NOT EDIT. + +package helpers + +import ( + mock "github.com/stretchr/testify/mock" + models "github.com/tech-thinker/gozen/models" +) + +// MockCodeHelper is an autogenerated mock type for the CodeHelper type +type MockCodeHelper struct { + mock.Mock +} + +// GenerateModel provides a mock function with given fields: doc +func (_m *MockCodeHelper) GenerateModel(doc models.Generator) error { + ret := _m.Called(doc) + + if len(ret) == 0 { + panic("no return value specified for GenerateModel") + } + + var r0 error + if rf, ok := ret.Get(0).(func(models.Generator) error); ok { + r0 = rf(doc) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMockCodeHelper creates a new instance of MockCodeHelper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockCodeHelper(t interface { + mock.TestingT + Cleanup(func()) +}) *MockCodeHelper { + mock := &MockCodeHelper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/cmd/helpers/projectHelper.go b/cmd/helpers/projectHelper.go index 17779a9..affa91b 100644 --- a/cmd/helpers/projectHelper.go +++ b/cmd/helpers/projectHelper.go @@ -42,8 +42,6 @@ func (h *projectHelper) SetupEnv(project models.Project) error { fileConfigs := []models.FileConfig{ {TemplatePath: "templates/env.sample.tmpl", Destination: "/.env"}, {TemplatePath: "templates/env.sample.tmpl", Destination: "/.env.sample"}, - {TemplatePath: "templates/env.sample.tmpl", Destination: "/docker/.env"}, - {TemplatePath: "templates/env.sample.tmpl", Destination: "/docker/.env.sample"}, } return h.systemRepo.WriteAll(project.GetAppDir(), fileConfigs, project) @@ -53,11 +51,11 @@ func (h *projectHelper) SetupDocker(project models.Project) error { fileConfigs := []models.FileConfig{ {TemplatePath: "templates/docker/Dockerfile.debug", Destination: "/docker/Dockerfile.debug"}, {TemplatePath: "templates/docker/Dockerfile.dev", Destination: "/docker/Dockerfile.dev"}, - {TemplatePath: "templates/docker/Dockerfile.prod", Destination: "/docker/Dockerfile.prod"}, - {TemplatePath: "templates/docker/docker-compose-debug.yml", Destination: "/docker/docker-compose-debug.yml"}, - {TemplatePath: "templates/docker/docker-compose.yml", Destination: "/docker/docker-compose.yml"}, {TemplatePath: "templates/docker/modd-debug.conf", Destination: "/docker/modd-debug.conf"}, {TemplatePath: "templates/docker/modd-dev.conf", Destination: "/docker/modd-dev.conf"}, + {TemplatePath: "templates/docker/Dockerfile.prod", Destination: "Dockerfile"}, + {TemplatePath: "templates/docker-compose-debug.yml.tmpl", Destination: "/docker-compose-debug.yml"}, + {TemplatePath: "templates/docker-compose.yml.tmpl", Destination: "/docker-compose.yml"}, } return h.systemRepo.WriteAll(project.GetAppDir(), fileConfigs, project) diff --git a/cmd/helpers/projectHelper_mock.go b/cmd/helpers/projectHelper_mock.go index 5ec7f5e..0e87dfe 100644 --- a/cmd/helpers/projectHelper_mock.go +++ b/cmd/helpers/projectHelper_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.50.0. DO NOT EDIT. +// Code generated by mockery v2.51.1. DO NOT EDIT. package helpers diff --git a/cmd/project.go b/cmd/project.go new file mode 100644 index 0000000..d5002cf --- /dev/null +++ b/cmd/project.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "fmt" + + "github.com/tech-thinker/gozen/models" + "github.com/urfave/cli/v2" +) + +func (c *app) CreateProject() *cli.Command { + + var packageName, outputDir, driver string + + return &cli.Command{ + Name: "create", + Usage: "Create new Projects.", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "pkg", + Aliases: []string{"p"}, + Value: "", + Usage: "Package name for new project.", + Destination: &packageName, + }, + &cli.StringFlag{ + Name: "output", + Aliases: []string{"o"}, + Value: ".", + Usage: "Output directory for new project.", + Destination: &outputDir, + }, + &cli.StringFlag{ + Name: "driver", + Aliases: []string{"d"}, + Value: "sqlite", + Usage: "Database driver for new project. eg. [sqlite, mysql, postgres]", + Destination: &driver, + }, + }, + Action: func(ctx *cli.Context) error { + project := models.Project{ + AppName: ctx.Args().Get(0), + PackageName: packageName, + Driver: driver, + WorkingDir: outputDir, + } + + err := project.Validate() + if err != nil { + fmt.Println(err) + return nil + } + + project.AutoFixes() + + return c.appService.CreateApp(project) + }, + } +} diff --git a/cmd/repository/project_mock.go b/cmd/repository/project_mock.go index f0fb49a..f7efb4b 100644 --- a/cmd/repository/project_mock.go +++ b/cmd/repository/project_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.50.0. DO NOT EDIT. +// Code generated by mockery v2.51.1. DO NOT EDIT. package repository diff --git a/cmd/repository/system_mock.go b/cmd/repository/system_mock.go index 7072332..8e245f0 100644 --- a/cmd/repository/system_mock.go +++ b/cmd/repository/system_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.50.0. DO NOT EDIT. +// Code generated by mockery v2.51.1. DO NOT EDIT. package repository diff --git a/cmd/service/createApp.go b/cmd/service/createApp.go index b84b396..37c45d9 100644 --- a/cmd/service/createApp.go +++ b/cmd/service/createApp.go @@ -8,11 +8,13 @@ import ( type AppService interface { CreateApp(project models.Project) error + GenerateModel(doc models.Generator) error } type appService struct { projectRepo repository.ProjectRepo projectHelper helpers.ProjectHelper + codeHelper helpers.CodeHelper } func (cmd *appService) CreateApp(project models.Project) error { @@ -88,12 +90,21 @@ func (cmd *appService) CreateApp(project models.Project) error { return nil } +func (cmd *appService) GenerateModel(doc models.Generator) error { + + return cmd.codeHelper.GenerateModel(doc) + + // return nil +} + func NewAppService( projectRepo repository.ProjectRepo, projectHelper helpers.ProjectHelper, + codeHelper helpers.CodeHelper, ) AppService { return &appService{ projectRepo: projectRepo, projectHelper: projectHelper, + codeHelper: codeHelper, } } diff --git a/cmd/service/createApp_test.go b/cmd/service/createApp_test.go index fbda783..c286500 100644 --- a/cmd/service/createApp_test.go +++ b/cmd/service/createApp_test.go @@ -14,6 +14,7 @@ func Test_appService_CreateApp(t *testing.T) { type fields struct { projectRepo *repository.MockProjectRepo projectHelper *helpers.MockProjectHelper + codeHelper *helpers.MockCodeHelper } type args struct { project models.Project @@ -346,6 +347,7 @@ func Test_appService_CreateApp(t *testing.T) { tt.fields = fields{ projectRepo: repository.NewMockProjectRepo(t), projectHelper: helpers.NewMockProjectHelper(t), + codeHelper: helpers.NewMockCodeHelper(t), } if tt.prepare != nil { @@ -355,6 +357,7 @@ func Test_appService_CreateApp(t *testing.T) { cmd := NewAppService( tt.fields.projectRepo, tt.fields.projectHelper, + tt.fields.codeHelper, ) if err := cmd.CreateApp(tt.args.project); (err != nil) != tt.wantErr { t.Errorf("appService.CreateApp() error = %v, wantErr %v", err, tt.wantErr) diff --git a/docs/README.md b/docs/README.md index 2fa9fe0..2db4988 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,36 +3,57 @@ ## Project Directory Structure ``` . -├── Makefile +├── .editorconfig +├── .env +├── .env.sample +├── .gitignore ├── app -│ ├── controllers -│ │ └── health.go -│ ├── initializer -│ │ └── service.go -│ └── router -│ └── router.go +│   ├── grpc +│   │   ├── handlers +│   │   │   └── health.go +│   │   ├── proto +│   │   │   ├── health_grpc.pb.go +│   │   │   ├── health.pb.go +│   │   │   └── health.proto +│   │   └── router +│   │   └── router.go +│   ├── init.go +│   └── rest +│   ├── controllers +│   │   └── health.go +│   └── router +│   └── router.go ├── config -│ └── config.go +│   └── config.go ├── constants -│ └── app.go +│   └── app.go +├── docker +│   ├── Dockerfile.debug +│   ├── Dockerfile.dev +│   ├── modd-debug.conf +│   └── modd-dev.conf +├── docker-compose-debug.yml +├── docker-compose.yml +├── Dockerfile ├── go.mod -├── go.sum ├── gozen.json ├── instance -│ ├── instance.go -│ └── registry -│ └── models.go +│   ├── instance.go +│   └── registry +│   └── models.go ├── logger -│ └── logger.go +│   └── logger.go ├── main.go +├── Makefile ├── models -│ └── health.go +│   └── health.go ├── repository -│ └── health.go +│   └── health.go ├── runner -│ └── api.go +│   ├── api.go +│   └── grpc.go ├── service -│ └── health.go +│   └── health.go └── utils └── utils.go ``` @@ -67,11 +88,11 @@ | Router | | Controllers | | |<-------------------------------+ | +-----------+ +-------------+-----+ - ^ | - | | - | | - | | - | v + ^ | + | | + | | + | | + | v +------------------+ +-----+-----------+ | +-------------------------->| | | Repository | | Service | diff --git a/main.go b/main.go index 81d22be..3377ea9 100644 --- a/main.go +++ b/main.go @@ -34,8 +34,9 @@ func main() { projectRepo := repository.NewProjectRepo(fileSystemWrapper) projectHelper := helpers.NewProjectHelper(systemRepo) + codeHelper := helpers.NewCodeHelper(systemRepo) - appSvc := service.NewAppService(projectRepo, projectHelper) + appSvc := service.NewAppService(projectRepo, projectHelper, codeHelper) app := cmd.NewApp(appSvc) cliApp := cli.NewApp() @@ -43,6 +44,7 @@ func main() { cliApp.Version = AppVersion cliApp.Commands = []*cli.Command{ app.CreateProject(), + app.GenerateModel(), } if err := cliApp.Run(os.Args); err != nil { fmt.Println(err) diff --git a/models/generate.go b/models/generate.go new file mode 100644 index 0000000..0b0a7e6 --- /dev/null +++ b/models/generate.go @@ -0,0 +1,9 @@ +package models + +type Generator struct { + Project + Type string `json:"type"` + InterfaceName string `json:"interfaceName"` + StructName string `json:"structName"` + RouteName string `json:"routeName"` +} diff --git a/templates/app/grpc/handlers/health.tmpl b/templates/app/grpc/handlers/health.tmpl index f4a4df6..f3c3274 100644 --- a/templates/app/grpc/handlers/health.tmpl +++ b/templates/app/grpc/handlers/health.tmpl @@ -16,7 +16,7 @@ type healthHandler struct { // Ping implementation for the Health service func (s *healthHandler) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PongReply, error) { - health, err := s.svc.HealthService().Ping(ctx) + health, err := s.svc.HealthSvc().Ping(ctx) if err != nil { logger.Log.Println(err) return &pb.PongReply{ diff --git a/templates/app/init.tmpl b/templates/app/init.tmpl index 9bbc575..99e2acb 100644 --- a/templates/app/init.tmpl +++ b/templates/app/init.tmpl @@ -9,7 +9,7 @@ import ( // ServiceRegistry is interface for all service entrypoint type ServiceRegistry interface { - HealthService() service.HealthSvc + HealthSvc() service.HealthSvc // TODO: add service dependency here } @@ -18,7 +18,7 @@ type serviceRegistry struct { // TODO: add service dependency here } -func (svc *serviceRegistry) HealthService() service.HealthSvc { +func (svc *serviceRegistry) HealthSvc() service.HealthSvc { return svc.healthSvc } @@ -27,13 +27,13 @@ func (svc *serviceRegistry) HealthService() service.HealthSvc { func Init(cfg config.Configuration, instance instance.Instance) ServiceRegistry { // Object init db := instance.DB() - + // Repository init healthRepo := repository.NewHealthRepo(db) - + // Service init healthSvc := service.NewHealthSvc(healthRepo) - + return &serviceRegistry{ healthSvc: healthSvc, } diff --git a/templates/app/rest/controllers/health.tmpl b/templates/app/rest/controllers/health.tmpl index c19e745..15f0c0e 100644 --- a/templates/app/rest/controllers/health.tmpl +++ b/templates/app/rest/controllers/health.tmpl @@ -9,6 +9,7 @@ import ( // HealthController is an interface for health controller type HealthController interface { + Route(router *gin.Engine) *gin.RouterGroup Ping(ctx *gin.Context) } @@ -16,8 +17,14 @@ type healthController struct { svc app.ServiceRegistry } +func (c *healthController) Route(router *gin.Engine) *gin.RouterGroup { + routerGroup := router.Group("/ping") + routerGroup.GET("", c.Ping) + return routerGroup +} + func (c *healthController) Ping(ctx *gin.Context) { - res, err := c.svc.HealthService().Ping(ctx) + res, err := c.svc.HealthSvc().Ping(ctx) if err!=nil { ctx.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/templates/app/rest/controllers/standard.tmpl b/templates/app/rest/controllers/standard.tmpl new file mode 100644 index 0000000..6f178ce --- /dev/null +++ b/templates/app/rest/controllers/standard.tmpl @@ -0,0 +1,181 @@ +package controllers + +import ( + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + + "{{.PackageName}}/app" + "{{.PackageName}}/models" +) + +// {{.InterfaceName}}Controller is an interface for {{.StructName}} controller +type {{.InterfaceName}}Controller interface { + Route(router *gin.Engine) *gin.RouterGroup + Create(ctx *gin.Context) + Get(ctx *gin.Context) + Update(ctx *gin.Context) + Delete(ctx *gin.Context) + List(ctx *gin.Context) +} + +type {{.StructName}}Controller struct { + svc app.ServiceRegistry +} + +func (c *{{.StructName}}Controller) Route(router *gin.Engine) *gin.RouterGroup { + routerGroup := router.Group("/{{.RouteName}}") + routerGroup.GET("", c.List) + routerGroup.POST("", c.Create) + routerGroup.GET("/:id", c.Get) + routerGroup.POST("/:id", c.Update) + routerGroup.DELETE("/:id", c.Delete) + return routerGroup +} + +func (c *{{.StructName}}Controller) Create(ctx *gin.Context) { + var req models.{{.InterfaceName}} + + // Bind JSON from request body to struct + if err := ctx.ShouldBindJSON(&req); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + + res, err := c.svc.{{.InterfaceName}}Svc().Create(ctx, req) + if err!=nil { + ctx.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + ctx.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "Created successfully.", + "body": res, + }) +} + +func (c *{{.StructName}}Controller) Get(ctx *gin.Context) { + // Extract the 'id' parameter from the URL path + id := ctx.Param("id") + + // Process the ID (e.g., convert it to an integer) + docId, err := strconv.Atoi(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + res, err := c.svc.{{.InterfaceName}}Svc().Get(ctx, docId) + if err!=nil { + ctx.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + ctx.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "Fetched successfully.", + "body": res, + }) +} + +func (c *{{.StructName}}Controller) Update(ctx *gin.Context) { + // Extract the 'id' parameter from the URL path + id := ctx.Param("id") + + // Process the ID (e.g., convert it to an integer) + docId, err := strconv.Atoi(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + + var req models.{{.InterfaceName}} + + // Bind JSON from request body to struct + if err := ctx.ShouldBindJSON(&req); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + + res, err := c.svc.{{.InterfaceName}}Svc().Update(ctx, docId, req) + if err!=nil { + ctx.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + ctx.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "Updated successfully.", + "body": res, + }) +} + +func (c *{{.StructName}}Controller) Delete(ctx *gin.Context) { + // Extract the 'id' parameter from the URL path + id := ctx.Param("id") + + // Process the ID (e.g., convert it to an integer) + docId, err := strconv.Atoi(id) + if err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + res, err := c.svc.{{.InterfaceName}}Svc().Delete(ctx, docId) + if err!=nil { + ctx.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + ctx.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "Deleted successfully.", + "body": res, + }) +} + +func (c *{{.StructName}}Controller) List(ctx *gin.Context) { + res, err := c.svc.{{.InterfaceName}}Svc().List(ctx) + if err!=nil { + ctx.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + ctx.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "Fetched successfully.", + "body": res, + }) +} + +// New{{.InterfaceName}}Controller initializes {{.StructName}} controller with dependency +func New{{.InterfaceName}}Controller(svc app.ServiceRegistry) {{.InterfaceName}}Controller { + return &{{.StructName}}Controller{ + svc: svc, + } +} diff --git a/templates/docker/docker-compose-debug.yml b/templates/docker-compose-debug.yml.tmpl similarity index 50% rename from templates/docker/docker-compose-debug.yml rename to templates/docker-compose-debug.yml.tmpl index 645ac97..5cfcace 100644 --- a/templates/docker/docker-compose-debug.yml +++ b/templates/docker-compose-debug.yml.tmpl @@ -1,12 +1,14 @@ version: "3" services: - app: - networks: - - app + {{.AppName}}: + container_name: {{.AppName}} build: context: . - dockerfile: Dockerfile.debug - container_name: app + dockerfile: docker/Dockerfile.debug + networks: + - {{.AppName}} + extra_hosts: + - host.docker.internal:host-gateway security_opt: - seccomp:unconfined env_file: .env @@ -14,7 +16,7 @@ services: - "2345:2345" - "3000:3000" volumes: - - "../:/app" + - "./:/app" networks: - app: + {{.AppName}} diff --git a/templates/docker-compose.yml.tmpl b/templates/docker-compose.yml.tmpl new file mode 100644 index 0000000..f562d9e --- /dev/null +++ b/templates/docker-compose.yml.tmpl @@ -0,0 +1,21 @@ +version: "3" +services: + {{.AppName}}: + container_name: {{.AppName}} + build: + context: . + dockerfile: docker/Dockerfile.dev + networks: + - {{.AppName}} + extra_hosts: + - host.docker.internal:host-gateway + security_opt: + - seccomp:unconfined + env_file: .env + ports: + - "3000:3000" + volumes: + - "./:/app" + +networks: + {{.AppName}} diff --git a/templates/docker/Dockerfile.debug b/templates/docker/Dockerfile.debug index 4bfee8f..9b3d303 100644 --- a/templates/docker/Dockerfile.debug +++ b/templates/docker/Dockerfile.debug @@ -36,6 +36,9 @@ RUN set -x && \ curl -sL "https://github.com/golang-migrate/migrate/releases/download/v4.18.1/$file" -o migrate.deb && \ dpkg -i migrate.deb && rm migrate.deb +# Installing networking tools +RUN apt install -y inetutils-ping + WORKDIR /app RUN go install github.com/go-delve/delve/cmd/dlv@latest diff --git a/templates/docker/Dockerfile.dev b/templates/docker/Dockerfile.dev index fcf18aa..6cb505f 100644 --- a/templates/docker/Dockerfile.dev +++ b/templates/docker/Dockerfile.dev @@ -36,6 +36,9 @@ RUN set -x && \ curl -sL "https://github.com/golang-migrate/migrate/releases/download/v4.18.1/$file" -o migrate.deb && \ dpkg -i migrate.deb && rm migrate.deb +# Installing networking tools +RUN apt install -y inetutils-ping + WORKDIR /app ENTRYPOINT ["modd", "-f", "./docker/modd-dev.conf"] diff --git a/templates/docker/Dockerfile.prod b/templates/docker/Dockerfile.prod index 57d868a..92d88f4 100644 --- a/templates/docker/Dockerfile.prod +++ b/templates/docker/Dockerfile.prod @@ -1,5 +1,5 @@ # Builder stage -FROM golang:1.22 as builder +FROM golang:1.22 AS builder WORKDIR /src ADD . . @@ -9,6 +9,7 @@ RUN go build -o app . FROM alpine:edge RUN apk add --no-cache libc6-compat RUN apk add --no-cache tzdata +RUN apk add --no-cache iputils-ping RUN mkdir /app WORKDIR /app COPY --from=builder /src/app /app/app diff --git a/templates/docker/docker-compose.yml b/templates/docker/docker-compose.yml deleted file mode 100644 index ea398fb..0000000 --- a/templates/docker/docker-compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: "3" -services: - app: - networks: - - app - build: - context: . - dockerfile: Dockerfile.dev - container_name: app - security_opt: - - seccomp:unconfined - env_file: .env - ports: - - "3000:3000" - volumes: - - "../:/app" - -networks: - app: diff --git a/templates/models/standard.tmpl b/templates/models/standard.tmpl new file mode 100644 index 0000000..6eb88f8 --- /dev/null +++ b/templates/models/standard.tmpl @@ -0,0 +1,11 @@ +package models + +import "{{.PackageName}}/instance/registry" + +type {{.InterfaceName}} struct { + ID uint +} + +func init() { + registry.RegisterModel(new({{.InterfaceName}})) +} diff --git a/templates/repository/standard.tmpl b/templates/repository/standard.tmpl new file mode 100644 index 0000000..b806f7d --- /dev/null +++ b/templates/repository/standard.tmpl @@ -0,0 +1,117 @@ +package repository + +import ( + "context" + "fmt" + + "gorm.io/gorm" + + "{{.PackageName}}/logger" + "{{.PackageName}}/models" +) + +type {{.InterfaceName}}Repo interface { + Insert(ctx context.Context, doc models.{{.InterfaceName}}) (uint, error) + InsertAll(ctx context.Context, docs []models.{{.InterfaceName}}) (bool, error) + Update(ctx context.Context, doc models.{{.InterfaceName}}, field string, value any) (bool, error) + UpdateAll(ctx context.Context, doc models.{{.InterfaceName}}) (bool, error) + Delete(ctx context.Context, doc models.{{.InterfaceName}}) (bool, error) + DeleteAll(ctx context.Context, ids []uint) (bool, error) + FindByID(ctx context.Context, id uint) (*models.{{.InterfaceName}}, error) + FindBy(ctx context.Context, key string, value any) (*models.{{.InterfaceName}}, error) + FindAll(ctx context.Context) ([]models.{{.InterfaceName}}, error) +} + +type {{.StructName}}Repo struct { + db *gorm.DB +} + +func (r *{{.StructName}}Repo) Insert(ctx context.Context, doc models.{{.InterfaceName}}) (uint, error) { + result := r.db.Create(doc) + if result.Error != nil { + logger.Log.Error(result.Error) + return 0, result.Error + } + return doc.ID, nil +} + +func (r *{{.StructName}}Repo) InsertAll(ctx context.Context, docs []models.{{.InterfaceName}}) (bool, error) { + result := r.db.Create(docs) + if result.Error != nil { + logger.Log.Error(result.Error) + return false, result.Error + } + return true, nil +} + +func (r *{{.StructName}}Repo) Update(ctx context.Context, doc models.{{.InterfaceName}}, field string, value any) (bool, error) { + result := r.db.Model(&doc).Update(field, value) + if result.Error != nil { + logger.Log.Error(result.Error) + return false, result.Error + } + return true, nil +} + +func (r *{{.StructName}}Repo) UpdateAll(ctx context.Context, doc models.{{.InterfaceName}}) (bool, error) { + result := r.db.Updates(doc) + if result.Error != nil { + logger.Log.Error(result.Error) + return false, result.Error + } + return true, nil +} + +func (r *{{.StructName}}Repo) Delete(ctx context.Context, doc models.{{.InterfaceName}}) (bool, error) { + result := r.db.Delete(doc) + if result.Error != nil { + logger.Log.Error(result.Error) + return false, result.Error + } + return true, nil +} + +func (r *{{.StructName}}Repo) DeleteAll(ctx context.Context, ids []uint) (bool, error) { + result := r.db.Where("ID in ?", ids).Delete(&models.{{.InterfaceName}}{}) + if result.Error != nil { + logger.Log.Error(result.Error) + return false, result.Error + } + return true, nil +} + +func (r *{{.StructName}}Repo) FindByID(ctx context.Context, id uint) (*models.{{.InterfaceName}}, error) { + var doc models.{{.InterfaceName}} + result := r.db.First(&doc, id) + if result.Error != nil { + logger.Log.Error(result.Error) + return &doc, result.Error + } + return &doc, nil +} + +func (r *{{.StructName}}Repo) FindBy(ctx context.Context, key string, value any) (*models.{{.InterfaceName}}, error) { + var doc models.{{.InterfaceName}} + result := r.db.Where(fmt.Sprintf(`%s=?`, key), value).First(&doc) + if result.Error != nil { + logger.Log.Error(result.Error) + return &doc, result.Error + } + return &doc, nil +} + +func (r *{{.StructName}}Repo) FindAll(ctx context.Context) ([]models.{{.InterfaceName}}, error) { + var docs []models.{{.InterfaceName}} + result := r.db.Find(docs) + if result.Error != nil { + logger.Log.Error(result.Error) + return docs, result.Error + } + return docs, nil +} + +func New{{.InterfaceName}}Repo(db *gorm.DB) {{.InterfaceName}}Repo { + return &{{.StructName}}Repo{ + db: db, + } +} diff --git a/templates/service/standard.tmpl b/templates/service/standard.tmpl new file mode 100644 index 0000000..2fd69fa --- /dev/null +++ b/templates/service/standard.tmpl @@ -0,0 +1,93 @@ +package service + +import ( + "context" + + "{{.PackageName}}/logger" + "{{.PackageName}}/models" + "{{.PackageName}}/repository" +) + +type {{.InterfaceName}}Svc interface { + Create(ctx context.Context, doc models.{{.InterfaceName}}) (*models.{{.InterfaceName}}, error) + Get(ctx context.Context, id int) (*models.{{.InterfaceName}}, error) + Update(ctx context.Context, id int, doc models.{{.InterfaceName}}) (*models.{{.InterfaceName}}, error) + Delete(ctx context.Context, id int) (*models.{{.InterfaceName}}, error) + List(ctx context.Context) ([]models.{{.InterfaceName}}, error) +} + +type {{.StructName}}Svc struct { + {{.StructName}}Repo repository.{{.InterfaceName}}Repo +} + +func (svc *{{.StructName}}Svc) Create(ctx context.Context, doc models.{{.InterfaceName}}) (*models.{{.InterfaceName}}, error) { + + id, err := svc.{{.StructName}}Repo.Insert(ctx, doc) + + if err != nil { + logger.Log.Error(err) + return nil, err + } + + doc.ID=id + + return &doc, nil +} + +func (svc *{{.StructName}}Svc) Get(ctx context.Context, id int) (*models.{{.InterfaceName}}, error) { + + doc, err := svc.{{.StructName}}Repo.FindByID(ctx, uint(id)) + + if err != nil { + logger.Log.Error(err) + return nil, err + } + + return doc, nil +} + +func (svc *{{.StructName}}Svc) Update(ctx context.Context, id int, doc models.{{.InterfaceName}}) (*models.{{.InterfaceName}}, error) { + doc.ID=uint(id) + + _, err := svc.{{.StructName}}Repo.UpdateAll(ctx, doc) + if err != nil { + logger.Log.Error(err) + return nil, err + } + + return &doc, nil +} + +func (svc *{{.StructName}}Svc) Delete(ctx context.Context, id int) (*models.{{.InterfaceName}}, error) { + doc := models.{{.InterfaceName}}{ + ID: uint(id), + } + + _, err := svc.{{.StructName}}Repo.Delete(ctx, doc) + + if err != nil { + logger.Log.Error(err) + return nil, err + } + + return &doc, nil +} + +func (svc *{{.StructName}}Svc) List(ctx context.Context) ([]models.{{.InterfaceName}}, error) { + + docs, err := svc.{{.StructName}}Repo.FindAll(ctx) + if err != nil { + logger.Log.Error(err) + return nil, err + } + + return docs, nil +} + +func New{{.InterfaceName}}Svc( + {{.StructName}}Repo repository.{{.InterfaceName}}Repo, +) {{.InterfaceName}}Svc { + return &{{.StructName}}Svc{ + {{.StructName}}Repo: {{.StructName}}Repo, + } +} diff --git a/utils/case_transform.go b/utils/case_transform.go new file mode 100644 index 0000000..b39a811 --- /dev/null +++ b/utils/case_transform.go @@ -0,0 +1,66 @@ +package utils + +import ( + "strings" + "unicode" +) + +// ToCamelCase converts a string to camel case (e.g., "myUser" -> "MyUser") +func ToCamelCase(s string) string { + var result strings.Builder + upperNext := true + + for _, r := range s { + if r == '_' || r == ' ' || r == '-' { + upperNext = true + } else { + if upperNext { + result.WriteRune(unicode.ToUpper(r)) + upperNext = false + } else { + result.WriteRune(r) // Preserve the original case + } + } + } + return result.String() +} + +// ToSnakeCase converts a string to snake case (e.g., "MyUser" -> "my_user") +func ToSnakeCase(s string) string { + if len(s) == 0 { + return s + } + + // Convert the first character to lowercase + result := strings.Builder{} + result.WriteRune(unicode.ToLower(rune(s[0]))) + + // Append the rest of the string as-is + result.WriteString(s[1:]) + + return result.String() +} + +// TransformString transforms the input string to camel case or snake case based on the input format +func TransformString(s string) (camelCase, snakeCase string) { + // Detect if the input is already in camel case or snake case + isCamelCase := false + for _, r := range s { + if unicode.IsUpper(r) { + isCamelCase = true + break + } + } + + if isCamelCase { + // Input is in camel case, convert to snake case + snakeCase = ToSnakeCase(s) + camelCase = ToCamelCase(snakeCase) // Ensure camel case format + } else { + // Input is in snake case or lowercase, convert to camel case + camelCase = ToCamelCase(s) + snakeCase = ToSnakeCase(camelCase) // Ensure snake case format + } + + return camelCase, snakeCase +} diff --git a/wrappers/fileSystemWrapper_mock.go b/wrappers/fileSystemWrapper_mock.go index 341a353..905e4fc 100644 --- a/wrappers/fileSystemWrapper_mock.go +++ b/wrappers/fileSystemWrapper_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.50.0. DO NOT EDIT. +// Code generated by mockery v2.51.1. DO NOT EDIT. package wrappers diff --git a/wrappers/shellWrapper_mock.go b/wrappers/shellWrapper_mock.go index 73b09f4..681e96f 100644 --- a/wrappers/shellWrapper_mock.go +++ b/wrappers/shellWrapper_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.50.0. DO NOT EDIT. +// Code generated by mockery v2.51.1. DO NOT EDIT. package wrappers