package main import ( "errors" "fmt" "net/http" "git.runcible.io/learning/pulley/internal/data" "git.runcible.io/learning/pulley/internal/validator" ) func (app *application) createMovieHandler(w http.ResponseWriter, r *http.Request) { // Declare an anonymous struct to hold the information that we expect to be in the // HTTP request body (note that the field names and types in the struct are a subset // of the Movie struct that we created earlier). This struct will be our *target // decode destination*. // just like encoding the field names need to be exported to have Decode set them var input struct { Title string `json:"title"` Year int32 `json:"year"` // implemented an Unmarshaler receiver for this // Runtime int32 `json:"runtime"` Runtime data.Runtime `json:"runtime"` Genres []string `json:"genres"` } // reads from the request body and decodes to our input struct // must be a non-nil pointer. Otherwise will raise json.InvalidUnmarshalError err := app.readJSON(w, r, &input) if err != nil { app.badRequestResponse(w, r, err) return } // use intermediate struct for decoding, use Movie struct for validation m := &data.Movie{ Title: input.Title, Year: input.Year, Runtime: input.Runtime, Genres: input.Genres, } v := validator.New() if validator.ValidateMovie(v, m); !v.Valid() { app.failedValidationResponse(w, r, v.Errors) } // We can implement a timeout context if we would like here ctx := r.Context() err = app.models.Movies.Insert(ctx, m) if err != nil { app.serverErrorResponse(w, r, err) } // Include location headers in HTTP response for newly created resource headers := make(http.Header) headers.Set("Location", fmt.Sprintf("/v1/movies/%d", m.ID)) // Write a JSON 201 Response with movie data in the response body and Location header err = app.writeJSON(w, http.StatusCreated, envelope{"movie": m}, headers) if err != nil { app.serverErrorResponse(w, r, err) } } func (app *application) getMovieHandler(w http.ResponseWriter, r *http.Request) { // When httprouter is parsing a request, any interpolated URL parameters will be // stored in the request context. We can use the ParamsFromContext() function to // retrieve a slice containing these parameter names and values. // TODO refactor id retrieval to an app.readIDParam receiver // params := httprouter.ParamsFromContext(r.Context()) // id, err := strconv.ParseInt(params.ByName("id"), 10, 64) id, err := app.readIDParam(r) if err != nil { app.notFoundResponse(w, r) return } movie, err := app.models.Movies.Get(r.Context(), id) if err != nil { switch { case errors.Is(err, data.ErrRecordNotFound): app.notFoundResponse(w, r) return default: app.serverErrorResponse(w, r, err) return } } err = app.writeJSON(w, http.StatusOK, envelope{"movie": movie}, nil) if err != nil { //app.logger.Error(err.Error()) //http.Error(w, "The server encountered a problem and could not process your request", http.StatusInternalServerError) app.serverErrorResponse(w, r, err) } } func (app *application) updateMovieHandler(w http.ResponseWriter, r *http.Request) { id, err := app.readIDParam(r) if err != nil { app.notFoundResponse(w, r) return } movie, err := app.models.Movies.Get(r.Context(), id) if err != nil { switch { case errors.Is(err, data.ErrRecordNotFound): app.notFoundResponse(w, r) default: app.serverErrorResponse(w, r, err) } return } var input struct { Title string `json:"title"` Year int32 `json:"year"` Runtime data.Runtime `json:"runtime"` Genres []string `json:"genres"` } err = app.readJSON(w, r, &input) if err != nil { app.badRequestResponse(w, r, err) return } movie.Title = input.Title movie.Year = input.Year movie.Runtime = input.Runtime movie.Genres = input.Genres v := validator.New() if validator.ValidateMovie(v, movie); !v.Valid() { app.failedValidationResponse(w, r, v.Errors) return } err = app.models.Movies.Update(r.Context(), movie) if err != nil { app.serverErrorResponse(w, r, err) return } err = app.writeJSON(w, http.StatusOK, envelope{"movie": movie}, nil) if err != nil { app.serverErrorResponse(w, r, err) } } func (app *application) healthCheckHandler(w http.ResponseWriter, r *http.Request) { // JSON has to be double quoted. FYI you would never really do this // js := `{"status": "available", "environment": %q, "version": %q}` // js = fmt.Sprintf(js, app.config.env, Version) // w.Write([]byte(js)) env := envelope{ "status": "available", "system_info": map[string]string{ "environment": app.config.Env, "version": Version, }, } // js, err := json.Marshal(data) // if err != nil { // app.logger.Error(err.Error()) // http.Error(w, "The server encountered a problem and could not process your request", http.StatusInternalServerError) // return // } // // Append a newline to the JSON. This is just a small nicety to make it easier to // // view in terminal applications. // js = append(js, '\n') // w.Header().Set("Content-Type", "application/json") // w.Write(js) err := app.writeJSON(w, 200, env, nil) if err != nil { //app.logger.Error(err.Error()) //http.Error(w, "The server encountered a problem and could not process your request", http.StatusInternalServerError) app.serverErrorResponse(w, r, err) return } }