feat(m1): backend scaffold - entries CRUD, start/stop, auth, migrations
This commit is contained in:
129
internal/handler/entry_handler.go
Normal file
129
internal/handler/entry_handler.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/wotra/wotra/internal/service"
|
||||
)
|
||||
|
||||
// EntryHandler serves /api/entries routes.
|
||||
type EntryHandler struct {
|
||||
svc *service.EntryService
|
||||
}
|
||||
|
||||
func NewEntryHandler(svc *service.EntryService) *EntryHandler {
|
||||
return &EntryHandler{svc: svc}
|
||||
}
|
||||
|
||||
func (h *EntryHandler) Routes(r chi.Router) {
|
||||
r.Post("/entries/start", h.Start)
|
||||
r.Post("/entries/{id}/stop", h.StopByID)
|
||||
r.Get("/entries", h.List)
|
||||
r.Put("/entries/{id}", h.Update)
|
||||
r.Delete("/entries/{id}", h.Delete)
|
||||
}
|
||||
|
||||
// Start POST /api/entries/start
|
||||
func (h *EntryHandler) Start(w http.ResponseWriter, r *http.Request) {
|
||||
var body struct {
|
||||
Note string `json:"note"`
|
||||
}
|
||||
_ = decodeJSON(r, &body) // note is optional
|
||||
|
||||
entry, err := h.svc.Start(r.Context(), body.Note)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, service.ErrEntryRunning):
|
||||
writeError(w, http.StatusConflict, err.Error())
|
||||
case errors.Is(err, service.ErrDayAlreadyClosed):
|
||||
writeError(w, http.StatusConflict, err.Error())
|
||||
default:
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, entry)
|
||||
}
|
||||
|
||||
// StopByID POST /api/entries/{id}/stop
|
||||
func (h *EntryHandler) StopByID(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
entry, err := h.svc.StopByID(r.Context(), id)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, service.ErrEntryNotFound):
|
||||
writeError(w, http.StatusNotFound, err.Error())
|
||||
case errors.Is(err, service.ErrEntryNotRunning):
|
||||
writeError(w, http.StatusConflict, err.Error())
|
||||
default:
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, entry)
|
||||
}
|
||||
|
||||
// List GET /api/entries?from=YYYY-MM-DD&to=YYYY-MM-DD
|
||||
func (h *EntryHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
from := r.URL.Query().Get("from")
|
||||
to := r.URL.Query().Get("to")
|
||||
if from == "" {
|
||||
from = "0000-01-01"
|
||||
}
|
||||
if to == "" {
|
||||
to = "9999-12-31"
|
||||
}
|
||||
entries, err := h.svc.List(r.Context(), from, to)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if entries == nil {
|
||||
writeJSON(w, http.StatusOK, []struct{}{})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, entries)
|
||||
}
|
||||
|
||||
// Update PUT /api/entries/{id}
|
||||
func (h *EntryHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
var body struct {
|
||||
StartTime *int64 `json:"start_time"`
|
||||
EndTime *int64 `json:"end_time"`
|
||||
Note *string `json:"note"`
|
||||
}
|
||||
if err := decodeJSON(r, &body); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid JSON")
|
||||
return
|
||||
}
|
||||
entry, err := h.svc.Update(r.Context(), id, service.UpdateEntryInput{
|
||||
StartTime: body.StartTime,
|
||||
EndTime: body.EndTime,
|
||||
Note: body.Note,
|
||||
})
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, service.ErrEntryNotFound):
|
||||
writeError(w, http.StatusNotFound, err.Error())
|
||||
case errors.Is(err, service.ErrCrossesMidnight):
|
||||
writeError(w, http.StatusUnprocessableEntity, err.Error())
|
||||
default:
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, entry)
|
||||
}
|
||||
|
||||
// Delete DELETE /api/entries/{id}
|
||||
func (h *EntryHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
if err := h.svc.Delete(r.Context(), id); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
Reference in New Issue
Block a user