Lesson 5
Adding Image Support to ToDo Items
Introduction to Adding Image Support

Welcome to this exciting lesson on enhancing our ToDo app by allowing it to support images for each item. In previous lessons, we've focused on adding and managing ToDo items using the Gin framework. Now, we're taking things a step further by adding image functionality to our app. This means that you'll be able to attach an image to any ToDo item, making your app more interactive and visually appealing. By the end of this lesson, you'll have learned how to upload and retrieve images effectively using Gin.

Modifying the ToDo Model

Before we can add image support, we need to modify the existing ToDo model. We can simply update it to include an ImagePath attribute, which will store the path of the uploaded image file. This modification will allow each ToDo item to have its own associated image (only one!).

Here's the modified Todo model:

Go
1package models 2 3type Todo struct { 4 ID int `json:"id"` 5 Title string `json:"title"` 6 Completed bool `json:"completed"` 7 ImagePath string `json:"imagePath"` 8}
Implementing Image Upload Functionality

Next, let's dive into implementing the functionality that allows image uploads. This involves setting up a POST route that accepts file uploads and updates the corresponding ToDo item with the image path.

controllers.go:

Go
1package controllers 2 3import ( 4 "net/http" 5 "strconv" 6 7 "github.com/gin-gonic/gin" 8 9 "codesignal.com/todoapp/services" 10) 11 12func UploadImage(c *gin.Context) { 13 id, err := strconv.Atoi(c.Param("id")) 14 if err != nil { 15 c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) 16 return 17 } 18 if success, imagePath := services.HandleImageUpload(c, id); success { 19 c.JSON(http.StatusOK, gin.H{"message": "Image added", "imagePath": imagePath}) 20 } else { 21 c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"}) 22 } 23}

services.go:

Go
1package services 2 3import ( 4 "path/filepath" 5 "strconv" 6 7 "github.com/gin-gonic/gin" 8 9 "codesignal.com/todoapp/models" 10) 11 12var todos []models.Todo // Assume this is initialized elsewhere 13 14func HandleImageUpload(c *gin.Context, id int) (bool, string) { 15 var todo *models.Todo 16 for i, t := range todos { 17 if t.ID == id { 18 todo = &todos[i] 19 } 20 } 21 if todo == nil { 22 c.JSON(http.StatusNotFound, gin.H{"error": "Todo not found"}) 23 return false, "" 24 } 25 26 file, err := c.FormFile("file") 27 if err != nil { 28 c.JSON(http.StatusBadRequest, gin.H{"error": "No file uploaded"}) 29 return false, "" 30 } 31 32 fileName := filepath.Base(file.Filename) 33 filePath := filepath.Join("uploads", fileName) 34 35 if err := c.SaveUploadedFile(file, filePath); err != nil { 36 return false, "" 37 } 38 todo.ImagePath = filePath 39 return true, filePath 40}

In this code snippet, we first convert the supplied ID from the URL parameter to an integer using strconv.Atoi, then locate the ToDo item by its ID. We use c.FormFile("file") to handle file uploads, which allows us to access files sent with the request. A check is performed to verify if a file is uploaded. If so, the file is saved to a designated directory using c.SaveUploadedFile(file, filePath), and the ImagePath attribute of the ToDo item is updated. Finally, a JSON response is returned using c.JSON, which sets the response status and sends JSON data back to the client. This response either confirms success or details an error.

Creating Routes for Image Download

Now that we have a way to upload images, the next step is to set up a GET route to retrieve and display these images as needed. Let's see how you can accomplish this.

controllers.go:

Go
1func GetImage(c *gin.Context) { 2 id, err := strconv.Atoi(c.Param("id")) 3 if err != nil { 4 c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) 5 return 6 } 7 if success, imagePath := services.RetrieveImage(id); success { 8 c.File(imagePath) 9 } else { 10 c.JSON(http.StatusNotFound, gin.H{"error": "Image not found"}) 11 } 12}

services.go:

Go
1func RetrieveImage(id int) (bool, string) { 2 for _, todo := range todos { 3 if todo.ID == id { 4 if todo.ImagePath != "" { 5 return true, todo.ImagePath 6 } 7 break 8 } 9 } 10 return false, "" 11}

The GET route allows retrieval of the image associated with a specific ToDo item by its ID. After converting the ID, the code searches for the matching ToDo item. We leverage c.Param("id") to retrieve the ID from the URL. The c.File(todo.ImagePath) method from *gin.Context is used to send a file in the HTTP response, enabling the image retrieval. If no image is found for the provided ID, an error message is returned. This structured approach ensures both successful image retrieval and appropriate error handling when issues arise.

Security Considerations: Preventing Path Traversal

When enhancing a web application with file upload capabilities, it's vital to secure this functionality against path traversal attacks. These attacks exploit vulnerabilities in the file system by providing a file path with malicious components, potentially enabling attackers to access unauthorized areas of the server's file hierarchy. To mitigate this risk, ensure user-supplied file paths are properly sanitized by extracting only the essential parts — specifically, the base name — before saving files to the server.

The relevant lines of code in the HandleImageUpload function defined above are the following:

Go
1fileName := filepath.Base(file.Filename) 2filePath := filepath.Join("uploads", fileName)
  • Utilizing filepath.Base: The function uses filepath.Base to ensure that files are stored using only their base names, effectively mitigating directory component manipulation that could lead to path traversal. For example, if filename is "images/photo.jpg", filepath.Base(filename) returns "photo.jpg", stripping away potentially harmful directory components.
  • Generating unique filenames and restricting access permissions: To enhance security against common threats, it's important to generate unique filenames for uploaded files and restrict access permissions to the uploads directory, ensuring that only authorized operations can be performed within this directory.
Summary and Conclusion

In this lesson, we expanded on our ToDo app's functionality by adding image support. You learned how to modify the ToDo model, set up routes for image upload and retrieval, and handle file paths effectively using the Gin framework. This enhancement allows each ToDo item to have a visual representation, enriching user interaction.

Congratulations on completing the final lesson of the course! You've gained valuable knowledge in building and extending a functional web application. Now it’s time to put your skills into practice with the exercises that follow. Well done on reaching this milestone!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.