Skip to content

GoCrudChallenge #2

Open
abe16s wants to merge 8 commits intoMereb-Tech:mainfrom
abe16s:main
Open

GoCrudChallenge #2
abe16s wants to merge 8 commits intoMereb-Tech:mainfrom
abe16s:main

Conversation

@abe16s
Copy link

@abe16s abe16s commented Oct 2, 2024

  • Done using gin framework
  • Implemented the basic CRUD functionalities
  • Followed clean architecture principle and separation of concerns

Abenezer Seifu

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a RESTful API for managing Person entities, including endpoints for creating, retrieving, updating, and deleting persons.
    • Added a service layer to handle business logic for Person management.
    • Implemented a repository pattern for data persistence of Person entities.
    • Configured a router with CORS support for handling cross-origin requests.
  • Bug Fixes

    • Improved error handling for various operations related to Person entities.
  • Documentation

    • Created foundational files for the application, including routing and service definitions.
  • Chores

    • Set up Go module dependencies for the application.

@coderabbitai
Copy link

coderabbitai bot commented Oct 2, 2024

Walkthrough

The changes introduce a CRUD application for managing Person entities in Go. Key components include a Controller for handling HTTP requests, a Repository for data storage, and a Service layer for business logic. The Person struct is defined to represent individual entities, and a router is set up to manage HTTP routes. The application utilizes the Gin framework for routing and includes necessary dependencies in the go.mod file. Overall, this implementation establishes a structured approach to handling Person data through RESTful API endpoints.

Changes

File Change Summary
controllers/controllers.go Added Controller struct with methods: NewController, CreatePerson, GetAllPersons, GetPersonById, UpdatePerson, DeletePerson.
domain/person.go Introduced Person struct with fields ID, Name, Age, Hobbies and a constructor NewPerson.
go.mod Created go.mod file specifying module and dependencies: gin and uuid, along with several indirect dependencies.
main.go Added entry point with main function initializing repository, service, controller, and router, running on localhost:8080.
repositories/repository.go Defined Repository struct with methods for CRUD operations: NewRepository, Create, GetAllPersons, GetPersonById, Update, Delete.
router/router.go Introduced router setup with NewRouter function defining routes for person management with CORS middleware.
usecases/IRepository.go Added IRepo interface defining methods for CRUD operations on Person entities.
usecases/Services.go Created IService interface and Service struct for managing Person entities with methods for CRUD operations and a constructor NewService.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Router
    participant Controller
    participant Service
    participant Repository

    Client->>Router: HTTP Request
    Router->>Controller: Call appropriate method
    Controller->>Service: Call service method
    Service->>Repository: Call repository method
    Repository-->>Service: Return data
    Service-->>Controller: Return data
    Controller-->>Router: Return response
    Router-->>Client: HTTP Response
Loading

🐰 "In the land of code where rabbits play,
New structs and methods brighten the day.
With controllers and services, oh what a sight,
CRUD for persons, everything feels right!
Hop along now, let’s run the show,
In our Go application, watch it grow!" 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between e0ddf38 and 41484b8.

📒 Files selected for processing (2)
  • repositories/repository.go (1 hunks)
  • usecases/Services.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • repositories/repository.go
  • usecases/Services.go

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Outside diff range and nitpick comments (10)
domain/person.go (3)

5-10: LGTM: Person struct is well-defined, with a minor formatting suggestion.

The Person struct is correctly defined with appropriate fields and tags. The use of uuid.UUID for the ID field is a good practice. The binding:"required" tag is correctly applied to the Name, Age, and Hobbies fields.

Consider adjusting the indentation for consistency:

 type Person struct {
-	ID      uuid.UUID   `json:"id"`
-    Name    string   	`json:"name" binding:"required"`
-    Age     int      	`json:"age" binding:"required"`
-    Hobbies []string 	`json:"hobbies" binding:"required"`
+	ID      uuid.UUID `json:"id"`
+	Name    string    `json:"name" binding:"required"`
+	Age     int       `json:"age" binding:"required"`
+	Hobbies []string  `json:"hobbies" binding:"required"`
 }

12-19: LGTM: NewPerson constructor is well-implemented, with a suggestion for input validation.

The NewPerson function correctly creates a new Person instance with a generated UUID. It properly sets all fields based on the input parameters.

Consider adding input validation to ensure data integrity:

func NewPerson(name string, age int, hobbies []string) (Person, error) {
	if name == "" {
		return Person{}, errors.New("name cannot be empty")
	}
	if age <= 0 {
		return Person{}, errors.New("age must be positive")
	}
	if len(hobbies) == 0 {
		return Person{}, errors.New("at least one hobby is required")
	}
	return Person{
		ID:      uuid.New(),
		Name:    name,
		Age:     age,
		Hobbies: hobbies,
	}, nil
}

This change would require updating the function's return type and error handling where it's called.


1-19: LGTM: Well-structured domain model with room for expansion.

The file is well-organized and focused on the Person domain model. It follows the single responsibility principle and provides a clear structure for representing and creating Person entities.

As the application grows, consider if additional methods or interfaces might be beneficial:

  1. Validation methods (e.g., IsValid() bool)
  2. String representation (e.g., String() string)
  3. Comparison methods (e.g., Equals(other Person) bool)
  4. Domain-specific behaviors or rules

These additions would depend on the specific requirements of your application and how the Person model is used throughout the system.

router/router.go (1)

24-28: LGTM: Routes are well-defined, but consider API versioning.

The routes for CRUD operations on the "person" resource are correctly defined and follow RESTful conventions. The use of path parameters for ID-based operations is appropriate.

Consider adding API versioning to your routes. This will make it easier to introduce breaking changes in the future without affecting existing clients. For example:

v1 := r.Group("/v1")
{
    v1.POST("/person", controller.CreatePerson)
    v1.GET("/person", controller.GetAllPersons)
    v1.GET("/person/:id", controller.GetPersonById)
    v1.PUT("/person/:id", controller.UpdatePerson)
    v1.DELETE("/person/:id", controller.DeletePerson)
}

This approach prefixes all routes with /v1, allowing you to introduce /v2 routes in the future if needed.

repositories/repository.go (4)

10-12: LGTM: Repository struct is well-defined.

The Repository struct is concise and appropriate for storing Person entities. Using UUID as the key is a good practice for unique identification, and the map allows for efficient lookups by ID.

Consider adding a brief comment describing the purpose of the Repository struct for improved documentation:

+// Repository manages a collection of Person entities identified by their UUID
 type Repository struct {
 	persons map[uuid.UUID]domain.Person
 }

20-23: Consider improving error handling in the Create method.

While the method correctly adds a person to the map, it always returns nil. Consider adding error checking, such as verifying if the person already exists or if the ID is valid.

Here's a suggested improvement:

 func (r *Repository) Create(person domain.Person) error {
+	if _, exists := r.persons[person.ID]; exists {
+		return errors.New("person with this ID already exists")
+	}
 	r.persons[person.ID] = person
 	return nil
 }

33-38: Consider returning a copy in GetPersonById method.

While the method correctly checks for the person's existence, returning a pointer to the original data might lead to unexpected modifications.

Consider returning a copy of the person:

 func (r *Repository) GetPersonById(id uuid.UUID) (*domain.Person, error) {
 	if person, ok := r.persons[id]; ok {
-		return &person, nil
+		personCopy := person
+		return &personCopy, nil
 	}
 	return nil, errors.New("person not found")
 }

1-56: Overall assessment: Good implementation with room for improvement.

The Repository implementation provides a solid foundation for managing Person entities in memory. It follows Go idioms and practices, and implements basic CRUD operations effectively. However, there are several areas where the implementation could be enhanced:

  1. Improve error handling in the Create method.
  2. Address potential issues with returning pointers in the GetAllPersons and GetPersonById methods.
  3. Implement a more flexible update mechanism in the Update method.
  4. Consider adding concurrency safety if the repository might be accessed by multiple goroutines.

These improvements would enhance the robustness and flexibility of the repository implementation.

usecases/Services.go (2)

8-14: Add documentation comments to exported interface and methods

The IService interface and its methods are exported but lack documentation comments. According to Go conventions, exported interfaces and methods should have comments that begin with the name of the interface or method. This improves code readability and aids in generating documentation.


16-22: Add documentation comments to exported struct and constructor

The Service struct and the NewService function are exported without associated documentation comments. Including comments that describe their purpose and usage enhances code maintainability and helps other developers understand their roles within the codebase.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 23798d7 and e0ddf38.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • controllers/controllers.go (1 hunks)
  • domain/person.go (1 hunks)
  • go.mod (1 hunks)
  • main.go (1 hunks)
  • repositories/repository.go (1 hunks)
  • router/router.go (1 hunks)
  • usecases/IRepository.go (1 hunks)
  • usecases/Services.go (1 hunks)
🔇 Additional comments (15)
usecases/IRepository.go (2)

1-1: LGTM: Appropriate package name.

The usecases package name is well-chosen for defining interfaces that represent use cases or business logic. It follows Go naming conventions.


3-6: LGTM: Appropriate and well-organized imports.

The import statements are correct and necessary for the interface definition. The use of the domain package suggests a clean architecture approach, while the uuid package is appropriately used for unique identification.

main.go (2)

3-8: LGTM: Imports are correctly structured and necessary.

The import statements are well-organized and include all the required packages for the application's CRUD functionality. They follow Go's standard import format and use the correct GitHub URLs for the module.


10-14: LGTM: Well-structured initialization of application components.

The main function follows a clear and logical order for initializing the application components. It demonstrates good use of dependency injection by passing each component to the next layer (repo -> service -> controller). This structure promotes loose coupling and makes the code more maintainable and testable.

domain/person.go (1)

1-3: LGTM: Package declaration and imports are correct.

The package name domain is appropriate for defining domain models, and the import of github.com/google/uuid is correctly used for UUID generation.

router/router.go (4)

3-10: LGTM: Imports are appropriate and well-organized.

The imports include necessary packages from the standard library and third-party libraries. The inclusion of the local controllers package indicates a good separation of concerns in the project structure.


12-12: LGTM: Function signature is well-defined.

The NewRouter function takes a controllers.Controller as a dependency and returns a *gin.Engine, which is appropriate for initializing a new router and allows for further customization if needed.


31-35: LGTM: Error handling for undefined routes is appropriate.

The catch-all route for handling undefined routes is well-implemented. It returns a 404 status code with a JSON error message, which is consistent with API best practices.


1-38: Overall, the router implementation is well-structured but has room for improvement.

The router/router.go file successfully implements a router for a CRUD application using the Gin framework. It includes necessary imports, CORS configuration, route definitions for person-related operations, and error handling for undefined routes.

Key strengths:

  1. Clear separation of concerns with the use of a controller.
  2. Well-defined routes following RESTful conventions.
  3. Appropriate error handling for undefined routes.

Areas for improvement:

  1. Tighten the CORS configuration for better security in production environments.
  2. Consider implementing API versioning to facilitate future updates.

These improvements will enhance the security and maintainability of your application. Great job on the overall implementation!

repositories/repository.go (3)

1-8: LGTM: Package declaration and imports are appropriate.

The package name "repositories" aligns well with the file's purpose and location. The imports are relevant to the implemented functionality, and there are no unused imports.


14-18: LGTM: NewRepository constructor is correctly implemented.

The constructor properly initializes the Repository with an empty map and returns a pointer to the Repository, which is a good practice for structs.


49-55: LGTM: Delete method is correctly implemented.

The Delete method effectively removes a person from the map, checks for the person's existence before deletion, and returns an appropriate error if the person is not found. The implementation is concise and effective.

usecases/Services.go (1)

24-27: Handle potential error before returning person in CreatePerson

In the CreatePerson method, you're returning person alongside the result of s.repo.Create(person). If the Create operation fails, it might be appropriate to return a zero-value Person or nil to indicate that the creation was unsuccessful.

Consider modifying the return statement:

func (s *Service) CreatePerson(name string, age int, hobbies []string) (domain.Person, error) {
    person := domain.NewPerson(name, age, hobbies)
-   return person, s.repo.Create(person)
+   err := s.repo.Create(person)
+   if err != nil {
+       return domain.Person{}, err
+   }
+   return person, nil
}

This ensures that if there's an error during creation, the caller doesn't receive an unpersisted person object.

controllers/controllers.go (2)

35-41: Efficient retrieval of all persons

The GetAllPersons method is implemented correctly, efficiently retrieving and returning all persons.


75-86: Proper error handling and response in DeletePerson

The DeletePerson method correctly handles errors and returns appropriate success messages.

Comment on lines +8 to +14
type IRepo interface {
Create(person domain.Person) error
GetAllPersons() ([]*domain.Person, error)
GetPersonById(id uuid.UUID) (*domain.Person, error)
Update(id uuid.UUID, person domain.Person) error
Delete(id uuid.UUID) error
} No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider enhancing the interface for better clarity and functionality.

The IRepo interface provides a good foundation for CRUD operations on Person entities. However, consider the following suggestions:

  1. The interface name IRepo could be more descriptive. Consider renaming it to PersonRepository for clarity.

  2. The Create method doesn't return the created Person or its ID. This might be useful in some cases, especially if the ID is generated on creation. Consider modifying it to:

    Create(person domain.Person) (uuid.UUID, error)
  3. For consistency, consider using pointer receivers for all methods that take a Person parameter:

    Create(person *domain.Person) (uuid.UUID, error)
    Update(id uuid.UUID, person *domain.Person) error
  4. The GetAllPersons method might benefit from pagination to handle large datasets efficiently. Consider adding offset and limit parameters:

    GetAllPersons(offset, limit int) ([]*domain.Person, error)

These changes would enhance the interface's functionality and make it more robust for various use cases.

What are your thoughts on these suggestions? Would you like me to propose a revised version of the interface?

Comment on lines +15 to +17
r := router.NewRouter(*controller)
r.Run("localhost:8080")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using an environment variable for the server address.

While using "localhost:8080" works for local development, it may limit the application's deployment flexibility. Consider using an environment variable to set the server address, allowing for easier configuration in different environments.

Here's a suggested improvement:

+ import "os"

func main() {
    // ... (previous code remains the same)

-   r.Run("localhost:8080")
+   port := os.Getenv("PORT")
+   if port == "" {
+       port = "8080"
+   }
+   r.Run(":" + port)
}

This change allows you to set the PORT environment variable when deploying, or it defaults to 8080 if not set. It also binds to all available network interfaces, making it more flexible for various deployment scenarios.

Committable suggestion was skipped due to low confidence.

Comment on lines +15 to +22
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Type"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider tightening CORS configuration for better security.

The current CORS configuration is very permissive, allowing all origins ("*"). While this might be acceptable for development, it's not recommended for production environments.

Consider the following suggestions:

  1. Replace AllowOrigins: []string{"*"} with a list of specific allowed origins.
  2. Review and potentially restrict the list of allowed methods and headers based on your API's requirements.
  3. Consider setting AllowCredentials: false unless you specifically need it to be true.

Example of a more restrictive configuration:

r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://yourdomain.com", "https://anotherdomain.com"},
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
    AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: false,
    MaxAge:           12 * time.Hour,
}))

Ensure to adjust the allowed origins, methods, and headers according to your specific requirements.

Comment on lines +25 to +31
func (r *Repository) GetAllPersons() ([]*domain.Person, error) {
allPersons := make([]*domain.Person, 0, len(r.persons))
for _, person := range r.persons {
allPersons = append(allPersons, &person)
}
return allPersons, nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential issue with returning pointers in GetAllPersons method.

The current implementation might lead to unexpected behavior due to how Go handles loop variables. All pointers in the returned slice might end up pointing to the same person.

Consider this alternative implementation:

 func (r *Repository) GetAllPersons() ([]*domain.Person, error) {
 	allPersons := make([]*domain.Person, 0, len(r.persons))
 	for _, person := range r.persons {
-		allPersons = append(allPersons, &person)
+		personCopy := person
+		allPersons = append(allPersons, &personCopy)
 	}
 	return allPersons, nil
 }

This ensures that each pointer in the slice points to a unique copy of each person.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (r *Repository) GetAllPersons() ([]*domain.Person, error) {
allPersons := make([]*domain.Person, 0, len(r.persons))
for _, person := range r.persons {
allPersons = append(allPersons, &person)
}
return allPersons, nil
}
func (r *Repository) GetAllPersons() ([]*domain.Person, error) {
allPersons := make([]*domain.Person, 0, len(r.persons))
for _, person := range r.persons {
personCopy := person
allPersons = append(allPersons, &personCopy)
}
return allPersons, nil
}

Comment on lines +28 to +29
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid exposing internal error messages to clients

Returning err.Error() directly in the response may reveal sensitive internal information to the client. It's better to return a generic error message and log the detailed error internally.

Apply this diff to all affected lines:

-ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})

Similarly, for not found errors:

-ctx.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
+ctx.JSON(http.StatusNotFound, gin.H{"error": "Person not found"})

This change applies to lines:

  • 28-29 in CreatePerson
  • 37-38 in GetAllPersons
  • 51-52 in GetPersonById
  • 69-70 in UpdatePerson
  • 82-83 in DeletePerson

Also applies to: 37-38, 51-52, 69-70, 82-83

Comment on lines +50 to +52
if err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Differentiate between not found and internal errors in GetPersonById

Currently, any error from c.service.GetPersonById(id) results in a StatusNotFound response. If the error is due to a server issue rather than the person not being found, a StatusInternalServerError would be more appropriate.

Consider modifying the error handling:

if err != nil {
    if errors.Is(err, domain.ErrPersonNotFound) {
        ctx.JSON(http.StatusNotFound, gin.H{"error": "Person not found"})
    } else {
        ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
    }
    return
}

return
}

person, err := c.service.CreatePerson(person.Name, person.Age, person.Hobbies)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid reassigning the 'person' variable to enhance code clarity

In line 26, the 'person' variable is reassigned after creating a new person. Reassigning the same variable can lead to confusion and potential bugs. Consider using a new variable to store the created person.

Apply this diff:

-person, err := c.service.CreatePerson(person.Name, person.Age, person.Hobbies)
+createdPerson, err := c.service.CreatePerson(person.Name, person.Age, person.Hobbies)

Update the response in line 31:

-ctx.JSON(http.StatusCreated, person)
+ctx.JSON(http.StatusCreated, createdPerson)

Committable suggestion was skipped due to low confidence.

Comment on lines +58 to +62
var person domain.Person
if err := ctx.ShouldBindJSON(&person); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent ID manipulation by excluding ID from the bound JSON

When updating a person, if the Person struct includes an ID field, a client could potentially change the ID in the JSON payload. To maintain data integrity, exclude the ID field during binding.

Apply this diff:

-var person domain.Person
+var personInput struct {
+    Name    string   `json:"name"`
+    Age     int      `json:"age"`
+    Hobbies []string `json:"hobbies"`
+}
-if err := ctx.ShouldBindJSON(&person); err != nil {
+if err := ctx.ShouldBindJSON(&personInput); err != nil {

Update the service call in line 68:

-if err := c.service.UpdatePerson(id, person.Name, person.Age, person.Hobbies); err != nil {
+if err := c.service.UpdatePerson(id, personInput.Name, personInput.Age, personInput.Hobbies); err != nil {

Committable suggestion was skipped due to low confidence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant