Melanjutkan dari modul sebelumnya, sekarang kita akan membuat handler untuk upload dan melakukan implementasi.
Buka file cmd/web/handlers.go, tambah fungsi UploadProfilePic dan helper function dan melakukan sedikit perbaikan pada fungsi render untuk assign value ke td.User jika session exists.
package main
import (
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"path"
"path/filepath"
"time"
"webapp/pkg/data"
)
var templatePath = "./templates/"
func (app *application) Home(w http.ResponseWriter, r *http.Request) {
var td = make(map[string]any)
if app.Session.Exists(r.Context(), "test") {
msg := app.Session.GetString(r.Context(), "test")
td["test"] = msg
} else {
app.Session.Put(r.Context(), "test", "Page visit at "+time.Now().UTC().String())
}
_ = app.render(w, r, "home.page.gohtml", &TemplateData{Data: td})
}
func (app *application) Profile(w http.ResponseWriter, r *http.Request) {
_ = app.render(w, r, "profile.page.gohtml", &TemplateData{})
}
type TemplateData struct {
IP string
Data map[string]any
Error string
Flash string
User data.User
}
func (app *application) render(w http.ResponseWriter, r *http.Request, t string, td *TemplateData) error {
//parse template
parsedTemplate, err := template.ParseFiles(path.Join(templatePath, t), path.Join(templatePath, "base.layout.gohtml"))
if err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return err
}
//gunakan middleware yang telah kita buat.
td.IP = app.ipFromContext(r.Context())
td.Error = app.Session.PopString(r.Context(), "error")
td.Flash = app.Session.PopString(r.Context(), "flash")
if app.Session.Exists(r.Context(), "user") {
td.User = app.Session.Get(r.Context(), "user").(data.User)
}
//execute template
err = parsedTemplate.Execute(w, td)
if err != nil {
return err
}
return nil
}
func (app *application) Login(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
log.Println(err)
http.Error(w, "bad request", http.StatusBadRequest)
return
}
//validation goes here
form := NewForm(r.PostForm)
form.Required("email", "password")
if !form.Valid() {
//redirect to login page with error message.
app.Session.Put(r.Context(), "error", "Invalid login credentials")
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
email := r.Form.Get("email")
password := r.Form.Get("password")
user, err := app.DB.GetUserByEmail(email)
if err != nil {
//redirect to login page with error message.
app.Session.Put(r.Context(), "error", "Invalid login")
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
//authenticate the user
// if not authenticated, redirect with error
if !app.authenticate(r, user, password) {
app.Session.Put(r.Context(), "error", "Invalid login")
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
//prevent fixation attack
_ = app.Session.RenewToken(r.Context())
//redirect to other page
app.Session.Put(r.Context(), "flash", "Successfully loged in")
http.Redirect(w, r, "user/profile", http.StatusSeeOther)
}
func (app *application) authenticate(r *http.Request, user *data.User, password string) bool {
if valid, err := user.PasswordMatches(password); err != nil || !valid {
return false
}
app.Session.Put(r.Context(), "user", user)
return true
}
func (app *application) UploadProfilePic(w http.ResponseWriter, r *http.Request) {
// call a function that extracts a file from an upload (request)
files, err := app.UploadFiles(r, "./static/img")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// get the user from the session
user := app.Session.Get(r.Context(), "user").(data.User)
// create a var of type data.UserImage
var i = data.UserImage{
UserID: user.ID,
FileName: files[0].OriginalFileName,
}
// insert the user image into user_images
_, err = app.DB.InsertUserImage(i)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// refresh the sessional variable "user"
updatedUser, err := app.DB.GetUser(user.ID)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
app.Session.Put(r.Context(), "user", updatedUser)
// redirect back to profile page
http.Redirect(w, r, "/user/profile", http.StatusSeeOther)
}
type UploadedFile struct {
OriginalFileName string
FileSize int64
}
func (app *application) UploadFiles(r *http.Request, uploadDir string) ([]*UploadedFile, error) {
var uploadedFiles []*UploadedFile
err := r.ParseMultipartForm(int64(1024 * 1024 * 5))
if err != nil {
return nil, fmt.Errorf("the uploaded file is too big, and must be less than %d bytes", 1024*1024*5)
}
for _, fHeaders := range r.MultipartForm.File {
for _, hdr := range fHeaders {
uploadedFiles, err = func(uploadedFiles []*UploadedFile) ([]*UploadedFile, error) {
var uploadedFile UploadedFile
infile, err := hdr.Open()
if err != nil {
return nil, err
}
defer infile.Close()
uploadedFile.OriginalFileName = hdr.Filename
var outfile *os.File
defer outfile.Close()
if outfile, err = os.Create(filepath.Join(uploadDir, uploadedFile.OriginalFileName)); nil != err {
return nil, err
} else {
fileSize, err := io.Copy(outfile, infile)
if err != nil {
return nil, err
}
uploadedFile.FileSize = fileSize
}
uploadedFiles = append(uploadedFiles, &uploadedFile)
return uploadedFiles, nil
}(uploadedFiles)
if err != nil {
return uploadedFiles, err
}
}
}
return uploadedFiles, nil
}
Kemudian tambahkan route pada file cmd/web/routes.go.
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func (app *application) routes() http.Handler {
mux := chi.NewRouter()
//register middleware
mux.Use(middleware.Recoverer)
mux.Use(app.addIPToContext)
mux.Use(app.Session.LoadAndSave) //middleware yang disediakan dari session manager
//register routes
mux.Get("/", app.Home)
mux.Post("/login", app.Login)
//gunakan middleware auth untuk route user profile
mux.Route("/user", func(mux chi.Router) {
mux.Use(app.auth)
mux.Get("/profile", app.Profile)
mux.Post("/upload-profile-pic", app.UploadProfilePic) // route untuk upload profile picture
})
//static assets
fileServer := http.FileServer(http.Dir("./static"))
mux.Handle("/static/*", http.StripPrefix("/static/", fileServer))
return mux
}
Jika kita test pada web browser, sesuai ekspektasi proses upload berhasil.

Pada modul selanjutnya kita akan membuat fungsi test untuk proses file upload.