2023-12-29 19:10:31 +01:00
|
|
|
package handler
|
2023-12-16 14:45:38 +01:00
|
|
|
|
|
|
|
import (
|
2023-12-29 19:10:31 +01:00
|
|
|
"context"
|
2023-12-16 14:45:38 +01:00
|
|
|
"database/sql"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log/slog"
|
|
|
|
"net/http"
|
|
|
|
|
2024-03-09 12:19:55 +01:00
|
|
|
"code.ewintr.nl/emdb/job"
|
2024-03-09 13:18:51 +01:00
|
|
|
"code.ewintr.nl/emdb/storage"
|
2023-12-16 14:45:38 +01:00
|
|
|
"github.com/google/uuid"
|
|
|
|
)
|
|
|
|
|
|
|
|
type MovieAPI struct {
|
2023-12-29 19:10:31 +01:00
|
|
|
apis APIIndex
|
2024-03-09 13:18:51 +01:00
|
|
|
repo *storage.MovieRepository
|
2023-12-30 09:19:53 +01:00
|
|
|
jq *job.JobQueue
|
2023-12-16 14:45:38 +01:00
|
|
|
logger *slog.Logger
|
|
|
|
}
|
|
|
|
|
2024-03-09 13:18:51 +01:00
|
|
|
func NewMovieAPI(apis APIIndex, repo *storage.MovieRepository, jq *job.JobQueue, logger *slog.Logger) *MovieAPI {
|
2023-12-16 14:45:38 +01:00
|
|
|
return &MovieAPI{
|
2023-12-29 19:10:31 +01:00
|
|
|
apis: apis,
|
2023-12-16 14:45:38 +01:00
|
|
|
repo: repo,
|
|
|
|
logger: logger.With("api", "movie"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
func (movieAPI *MovieAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
logger := movieAPI.logger.With("method", "serveHTTP")
|
|
|
|
|
2023-12-30 09:19:53 +01:00
|
|
|
head, tail := ShiftPath(r.URL.Path)
|
|
|
|
subHead, subTail := ShiftPath(tail)
|
2023-12-29 19:10:31 +01:00
|
|
|
for aPath, api := range movieAPI.apis {
|
2023-12-30 09:19:53 +01:00
|
|
|
if head != "" && subHead == fmt.Sprintf("%s", aPath) {
|
2023-12-29 19:10:31 +01:00
|
|
|
r.URL.Path = subTail
|
2023-12-30 09:19:53 +01:00
|
|
|
r = r.Clone(context.WithValue(r.Context(), MovieKey, head))
|
2023-12-29 19:10:31 +01:00
|
|
|
api.ServeHTTP(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2023-12-16 14:45:38 +01:00
|
|
|
|
|
|
|
switch {
|
2023-12-30 09:19:53 +01:00
|
|
|
case r.Method == http.MethodGet && head != "":
|
|
|
|
movieAPI.Read(w, r, head)
|
|
|
|
case r.Method == http.MethodPut && head != "":
|
|
|
|
movieAPI.Store(w, r, head)
|
|
|
|
case r.Method == http.MethodPost && head == "":
|
2023-12-29 19:10:31 +01:00
|
|
|
movieAPI.Store(w, r, "")
|
2023-12-30 09:19:53 +01:00
|
|
|
case r.Method == http.MethodDelete && head != "":
|
|
|
|
movieAPI.Delete(w, r, head)
|
|
|
|
case r.Method == http.MethodGet && head == "":
|
2023-12-29 19:10:31 +01:00
|
|
|
movieAPI.List(w, r)
|
2023-12-16 14:45:38 +01:00
|
|
|
default:
|
2023-12-30 09:19:53 +01:00
|
|
|
Error(w, http.StatusNotFound, "unregistered path", fmt.Errorf("method %q with subpath %q was not registered in /movie", r.Method, head), logger)
|
2023-12-16 14:45:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
func (movieAPI *MovieAPI) Read(w http.ResponseWriter, r *http.Request, movieID string) {
|
|
|
|
logger := movieAPI.logger.With("method", "read")
|
2023-12-16 14:45:38 +01:00
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
m, err := movieAPI.repo.FindOne(movieID)
|
2023-12-16 14:45:38 +01:00
|
|
|
switch {
|
|
|
|
case errors.Is(err, sql.ErrNoRows):
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
fmt.Fprint(w, `{"message":"not found"}`)
|
|
|
|
return
|
|
|
|
case err != nil:
|
|
|
|
Error(w, http.StatusInternalServerError, "could not get movie", err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
resJson, err := json.Marshal(m)
|
2023-12-16 14:45:38 +01:00
|
|
|
if err != nil {
|
|
|
|
Error(w, http.StatusInternalServerError, "could not marshal response", err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprint(w, string(resJson))
|
|
|
|
}
|
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
func (movieAPI *MovieAPI) Store(w http.ResponseWriter, r *http.Request, urlID string) {
|
|
|
|
logger := movieAPI.logger.With("method", "create")
|
2023-12-16 14:45:38 +01:00
|
|
|
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
Error(w, http.StatusBadRequest, "could not read body", err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
2024-03-09 13:18:51 +01:00
|
|
|
var m storage.Movie
|
2023-12-29 19:10:31 +01:00
|
|
|
if err := json.Unmarshal(body, &m); err != nil {
|
2023-12-16 14:45:38 +01:00
|
|
|
Error(w, http.StatusBadRequest, "could not unmarshal request body", err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-17 11:47:55 +01:00
|
|
|
switch {
|
2023-12-29 19:10:31 +01:00
|
|
|
case urlID == "" && m.ID == "":
|
|
|
|
m.ID = uuid.New().String()
|
|
|
|
case urlID != "" && m.ID == "":
|
|
|
|
m.ID = urlID
|
|
|
|
case urlID != "" && m.ID != "" && urlID != m.ID:
|
2023-12-17 11:47:55 +01:00
|
|
|
Error(w, http.StatusBadRequest, "id in path does not match id in body", err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
if err := movieAPI.repo.Store(m); err != nil {
|
2023-12-16 14:45:38 +01:00
|
|
|
Error(w, http.StatusInternalServerError, "could not store movie", err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
resBody, err := json.Marshal(m)
|
2023-12-16 14:45:38 +01:00
|
|
|
if err != nil {
|
|
|
|
Error(w, http.StatusInternalServerError, "could not marshal movie", err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprint(w, string(resBody))
|
|
|
|
}
|
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
func (movieAPI *MovieAPI) Delete(w http.ResponseWriter, r *http.Request, urlID string) {
|
|
|
|
logger := movieAPI.logger.With("method", "delete")
|
2023-12-17 11:47:55 +01:00
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
err := movieAPI.repo.Delete(urlID)
|
2023-12-25 12:06:02 +01:00
|
|
|
switch {
|
|
|
|
case errors.Is(err, sql.ErrNoRows):
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
fmt.Fprint(w, `{"message":"not found"}`)
|
|
|
|
return
|
|
|
|
case err != nil:
|
2023-12-17 11:47:55 +01:00
|
|
|
Error(w, http.StatusInternalServerError, "could not delete movie", err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
func (movieAPI *MovieAPI) List(w http.ResponseWriter, r *http.Request) {
|
|
|
|
logger := movieAPI.logger.With("method", "list")
|
2023-12-16 14:45:38 +01:00
|
|
|
|
2023-12-29 19:10:31 +01:00
|
|
|
movies, err := movieAPI.repo.FindAll()
|
2023-12-16 14:45:38 +01:00
|
|
|
if err != nil {
|
|
|
|
Error(w, http.StatusInternalServerError, "could not get movies", err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
resBody, err := json.Marshal(movies)
|
|
|
|
if err != nil {
|
|
|
|
Error(w, http.StatusInternalServerError, "could not marshal movies", err, logger)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprint(w, string(resBody))
|
|
|
|
}
|