diff --git a/.gitignore b/.gitignore index 6bc00644..0226c18e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ coverage.out recipe.db bin go.work.sum -fuego \ No newline at end of file +/cmd/fuego/fuego \ No newline at end of file diff --git a/cmd/fuego/commands/controller.go b/cmd/fuego/commands/controller.go index 82c03b7c..b4819467 100644 --- a/cmd/fuego/commands/controller.go +++ b/cmd/fuego/commands/controller.go @@ -18,35 +18,54 @@ func Controller() *cli.Command { Usage: "creates a new controller file", Aliases: []string{"c"}, Action: func(cCtx *cli.Context) error { - controllerName := cCtx.Args().First() + entityName := cCtx.Args().First() - if controllerName == "" { - controllerName = "newController" + if entityName == "" { + entityName = "newEntity" fmt.Println("Note: You can add a controller name as an argument. Example: `fuego controller books`") } - _, err := createController(controllerName) + _, err := createNewEntityDomainFile(entityName, "entity.go", entityName+".go") if err != nil { return err } - fmt.Printf("🔥 Controller %s created successfully\n", controllerName) + _, err = createNewEntityDomainFile(entityName, "controller.go", entityName+"Controller.go") + if err != nil { + return err + } + + fmt.Printf("🔥 Controller %s created successfully\n", entityName) return nil }, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "with-service", + Usage: "enable service file generation", + Value: false, + Action: func(cCtx *cli.Context, shouldGenerateServiceFile bool) error { + if !shouldGenerateServiceFile { + return nil + } + + return serviceCommandAction(cCtx) + }, + }, + }, } } // createController creates a new controller file -func createController(controllerName string) (string, error) { - controllerDir := "./controller/" +func createNewEntityDomainFile(entityName, controllerTemplateFileName, outputFileName string) (string, error) { + controllerDir := "./domains/" + entityName + "/" if _, err := os.Stat(controllerDir); os.IsNotExist(err) { - err = os.Mkdir(controllerDir, 0o755) + err = os.MkdirAll(controllerDir, 0o755) if err != nil { return "", err } } - templateContent, err := templates.FS.ReadFile("controller/controller.go") + templateContent, err := templates.FS.ReadFile("newEntity/" + controllerTemplateFileName) if err != nil { return "", err } @@ -54,10 +73,10 @@ func createController(controllerName string) (string, error) { t := language.English titler := cases.Title(t) - newContent := strings.ReplaceAll(string(templateContent), "newController", controllerName) - newContent = strings.ReplaceAll(newContent, "NewController", titler.String(controllerName)) + newContent := strings.ReplaceAll(string(templateContent), "newEntity", entityName) + newContent = strings.ReplaceAll(newContent, "NewEntity", titler.String(entityName)) - controllerPath := fmt.Sprintf("%s%s.go", controllerDir, controllerName) + controllerPath := fmt.Sprintf("%s%s", controllerDir, outputFileName) err = os.WriteFile(controllerPath, []byte(newContent), 0o644) if err != nil { diff --git a/cmd/fuego/commands/controller_test.go b/cmd/fuego/commands/controller_test.go index 111d9423..fc30dc5d 100644 --- a/cmd/fuego/commands/controller_test.go +++ b/cmd/fuego/commands/controller_test.go @@ -8,7 +8,7 @@ import ( ) func TestCreateController(t *testing.T) { - res, err := createController("books") + res, err := createNewEntityDomainFile("books", "controller.go", "booksController.go") require.NoError(t, err) require.Contains(t, res, "package controller") require.Contains(t, res, `fuego.Get(booksGroup, "/{id}", rs.getBooks)`) diff --git a/cmd/fuego/commands/service.go b/cmd/fuego/commands/service.go new file mode 100644 index 00000000..c1133cbc --- /dev/null +++ b/cmd/fuego/commands/service.go @@ -0,0 +1,38 @@ +package commands + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +func Service() *cli.Command { + return &cli.Command{ + Name: "service", + Usage: "creates a new service file", + Aliases: []string{"s"}, + Action: serviceCommandAction, + } +} + +func serviceCommandAction(cCtx *cli.Context) error { + entityName := cCtx.Args().First() + + if entityName == "" { + entityName = "newController" + fmt.Println("Note: You can add an entity name as an argument. Example: `fuego service books`") + } + + _, err := createNewEntityDomainFile(entityName, "entity.go", entityName+".go") + if err != nil { + return err + } + + _, err = createNewEntityDomainFile(entityName, "service.go", entityName+"Service.go") + if err != nil { + return err + } + + fmt.Printf("🔥 Service %s created successfully\n", entityName) + return nil +} diff --git a/cmd/fuego/main.go b/cmd/fuego/main.go index d40c7ada..2e0ef6a2 100644 --- a/cmd/fuego/main.go +++ b/cmd/fuego/main.go @@ -20,6 +20,7 @@ func main() { }, Commands: []*cli.Command{ commands.Controller(), + commands.Service(), }, } diff --git a/cmd/fuego/templates/controller/controller.go b/cmd/fuego/templates/controller/controller.go deleted file mode 100644 index 9c636818..00000000 --- a/cmd/fuego/templates/controller/controller.go +++ /dev/null @@ -1,86 +0,0 @@ -package controller - -import ( - "github.com/go-fuego/fuego" -) - -type NewControllerResources struct { - // TODO add resources - NewControllerService NewControllerService -} - -type NewController struct { - ID string `json:"id"` - Name string `json:"name"` -} - -type NewControllerCreate struct { - Name string `json:"name"` -} - -type NewControllerUpdate struct { - Name string `json:"name"` -} - -func (rs NewControllerResources) Routes(s *fuego.Server) { - newControllerGroup := fuego.Group(s, "/newController") - - fuego.Get(newControllerGroup, "/", rs.getAllNewController) - fuego.Post(newControllerGroup, "/", rs.postNewController) - - fuego.Get(newControllerGroup, "/{id}", rs.getNewController) - fuego.Put(newControllerGroup, "/{id}", rs.putNewController) - fuego.Delete(newControllerGroup, "/{id}", rs.deleteNewController) -} - -func (rs NewControllerResources) getAllNewController(c fuego.ContextNoBody) ([]NewController, error) { - return rs.NewControllerService.GetAllNewController() -} - -func (rs NewControllerResources) postNewController(c *fuego.ContextWithBody[NewControllerCreate]) (NewController, error) { - body, err := c.Body() - if err != nil { - return NewController{}, err - } - - new, err := rs.NewControllerService.CreateNewController(body) - if err != nil { - return NewController{}, err - } - - return new, nil -} - -func (rs NewControllerResources) getNewController(c fuego.ContextNoBody) (NewController, error) { - id := c.PathParam("id") - - return rs.NewControllerService.GetNewController(id) -} - -func (rs NewControllerResources) putNewController(c *fuego.ContextWithBody[NewControllerUpdate]) (NewController, error) { - id := c.PathParam("id") - - body, err := c.Body() - if err != nil { - return NewController{}, err - } - - new, err := rs.NewControllerService.UpdateNewController(id, body) - if err != nil { - return NewController{}, err - } - - return new, nil -} - -func (rs NewControllerResources) deleteNewController(c *fuego.ContextNoBody) (any, error) { - return rs.NewControllerService.DeleteNewController(c.PathParam("id")) -} - -type NewControllerService interface { - GetNewController(id string) (NewController, error) - CreateNewController(NewControllerCreate) (NewController, error) - GetAllNewController() ([]NewController, error) - UpdateNewController(id string, input NewControllerUpdate) (NewController, error) - DeleteNewController(id string) (any, error) -} diff --git a/cmd/fuego/templates/newEntity/controller.go b/cmd/fuego/templates/newEntity/controller.go new file mode 100644 index 00000000..39bf42d9 --- /dev/null +++ b/cmd/fuego/templates/newEntity/controller.go @@ -0,0 +1,55 @@ +package newEntity + +import ( + "github.com/go-fuego/fuego" +) + +type NewEntityResources struct { + // TODO add resources + NewEntityService NewEntityService +} + +func (rs NewEntityResources) Routes(s *fuego.Server) { + newEntityGroup := fuego.Group(s, "/newEntity") + + fuego.Get(newEntityGroup, "/", rs.getAllNewEntity) + fuego.Post(newEntityGroup, "/", rs.postNewEntity) + + fuego.Get(newEntityGroup, "/{id}", rs.getNewEntity) + fuego.Put(newEntityGroup, "/{id}", rs.putNewEntity) + fuego.Delete(newEntityGroup, "/{id}", rs.deleteNewEntity) +} + +func (rs NewEntityResources) getAllNewEntity(c fuego.ContextNoBody) ([]NewEntity, error) { + return rs.NewEntityService.GetAllNewEntity() +} + +func (rs NewEntityResources) postNewEntity(c *fuego.ContextWithBody[NewEntityCreate]) (NewEntity, error) { + body, err := c.Body() + if err != nil { + return NewEntity{}, err + } + + return rs.NewEntityService.CreateNewEntity(body) +} + +func (rs NewEntityResources) getNewEntity(c fuego.ContextNoBody) (NewEntity, error) { + id := c.PathParam("id") + + return rs.NewEntityService.GetNewEntity(id) +} + +func (rs NewEntityResources) putNewEntity(c *fuego.ContextWithBody[NewEntityUpdate]) (NewEntity, error) { + id := c.PathParam("id") + + body, err := c.Body() + if err != nil { + return NewEntity{}, err + } + + return rs.NewEntityService.UpdateNewEntity(id, body) +} + +func (rs NewEntityResources) deleteNewEntity(c *fuego.ContextNoBody) (any, error) { + return rs.NewEntityService.DeleteNewEntity(c.PathParam("id")) +} diff --git a/cmd/fuego/templates/newEntity/entity.go b/cmd/fuego/templates/newEntity/entity.go new file mode 100644 index 00000000..cdba5176 --- /dev/null +++ b/cmd/fuego/templates/newEntity/entity.go @@ -0,0 +1,22 @@ +package newEntity + +type NewEntity struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type NewEntityCreate struct { + Name string `json:"name"` +} + +type NewEntityUpdate struct { + Name string `json:"name"` +} + +type NewEntityService interface { + GetNewEntity(id string) (NewEntity, error) + CreateNewEntity(NewEntityCreate) (NewEntity, error) + GetAllNewEntity() ([]NewEntity, error) + UpdateNewEntity(id string, input NewEntityUpdate) (NewEntity, error) + DeleteNewEntity(id string) (any, error) +} diff --git a/cmd/fuego/templates/newEntity/service.go b/cmd/fuego/templates/newEntity/service.go new file mode 100644 index 00000000..3a2047fb --- /dev/null +++ b/cmd/fuego/templates/newEntity/service.go @@ -0,0 +1,90 @@ +package newEntity + +import ( + "fmt" + "sync" + "time" + + "github.com/go-fuego/fuego" +) + +type NewEntityServiceImpl struct { + newEntityRepository map[string]NewEntity + mu sync.RWMutex +} + +var _ NewEntityService = &NewEntityServiceImpl{} + +func NewNewEntityService() NewEntityService { + return &NewEntityServiceImpl{ + newEntityRepository: make(map[string]NewEntity), + } +} + +func (bs *NewEntityServiceImpl) GetNewEntity(id string) (NewEntity, error) { + bs.mu.RLock() + defer bs.mu.RUnlock() + + newEntity, exists := bs.newEntityRepository[id] + if !exists { + return NewEntity{}, fuego.NotFoundError{Title: "NewEntity not found with id " + id} + } + + return newEntity, nil +} + +func (bs *NewEntityServiceImpl) CreateNewEntity(input NewEntityCreate) (NewEntity, error) { + bs.mu.Lock() + defer bs.mu.Unlock() + + id := fmt.Sprintf("%d", time.Now().UnixNano()) + newEntity := NewEntity{ + ID: id, + Name: input.Name, + } + + bs.newEntityRepository[id] = newEntity + return newEntity, nil +} + +func (bs *NewEntityServiceImpl) GetAllNewEntity() ([]NewEntity, error) { + bs.mu.RLock() + defer bs.mu.RUnlock() + + allNewEntity := make([]NewEntity, 0, len(bs.newEntityRepository)) + for _, newEntity := range bs.newEntityRepository { + allNewEntity = append(allNewEntity, newEntity) + } + + return allNewEntity, nil +} + +func (bs *NewEntityServiceImpl) UpdateNewEntity(id string, input NewEntityUpdate) (NewEntity, error) { + bs.mu.Lock() + defer bs.mu.Unlock() + + newEntity, exists := bs.newEntityRepository[id] + if !exists { + return NewEntity{}, fuego.NotFoundError{Title: "NewEntity not found with id " + id} + } + + if input.Name != "" { + newEntity.Name = input.Name + } + + bs.newEntityRepository[id] = newEntity + return newEntity, nil +} + +func (bs *NewEntityServiceImpl) DeleteNewEntity(id string) (any, error) { + bs.mu.Lock() + defer bs.mu.Unlock() + + _, exists := bs.newEntityRepository[id] + if !exists { + return nil, fuego.NotFoundError{Title: "NewEntity not found with id " + id} + } + + delete(bs.newEntityRepository, id) + return "deleted", nil +} diff --git a/documentation/docs/tutorials/02-crud.md b/documentation/docs/tutorials/02-crud.md index 74c7b202..b76a36a9 100644 --- a/documentation/docs/tutorials/02-crud.md +++ b/documentation/docs/tutorials/02-crud.md @@ -14,7 +14,13 @@ fuego controller books go run github.com/go-fuego/fuego/cmd/fuego@latest controller books ``` -This generates a controller and a service for the `books` resource. +This generates a controller for the `books` resource. + +:::tip + +Use `fuego controller --with-service books` to generate a simple map based in memory service so you can pass it to the controller resources to quickly interact with your new book entity! + +::: You then have to implement the service interface in the controller to be able to play with data. It's a form of **dependency injection** that we chose to use