refactor: repositories (#28)

* cleanup unnecessary mocks

* refactor document storage location

* remove unused function

* move ownership to document types to repo package

* move types and mappers to repo package

* refactor sets to own package
This commit is contained in:
Hayden 2022-09-27 15:52:13 -08:00 committed by GitHub
parent 2e82398e5c
commit 343290a55a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
79 changed files with 3169 additions and 3160 deletions

View file

@ -4,7 +4,6 @@ import (
"net/http"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/types"
"github.com/hay-kot/homebox/backend/pkgs/server"
)
@ -19,6 +18,22 @@ type V1Controller struct {
maxUploadSize int64
}
type (
Build struct {
Version string `json:"version"`
Commit string `json:"commit"`
BuildTime string `json:"buildTime"`
}
ApiSummary struct {
Healthy bool `json:"health"`
Versions []string `json:"versions"`
Title string `json:"title"`
Message string `json:"message"`
Build Build
}
)
func BaseUrlFunc(prefix string) func(s string) string {
v1Base := prefix + "/v1"
prefixFunc := func(s string) string {
@ -42,11 +57,11 @@ type ReadyFunc func() bool
// @Summary Retrieves the basic information about the API
// @Tags Base
// @Produce json
// @Success 200 {object} types.ApiSummary
// @Success 200 {object} ApiSummary
// @Router /v1/status [GET]
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build types.Build) http.HandlerFunc {
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
server.Respond(w, http.StatusOK, types.ApiSummary{
server.Respond(w, http.StatusOK, ApiSummary{
Healthy: ready(),
Title: "Go API Template",
Message: "Welcome to the Go API Template Application!",

View file

@ -1,47 +0,0 @@
package v1
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/hay-kot/homebox/backend/internal/types"
"github.com/stretchr/testify/assert"
)
func Test_NewHandlerV1(t *testing.T) {
v1Base := BaseUrlFunc("/testing/v1")
ctrl := NewControllerV1(mockHandler.svc)
assert.NotNil(t, ctrl)
assert.Equal(t, "/testing/v1/v1/abc123", v1Base("/abc123"))
assert.Equal(t, "/testing/v1/v1/abc123", v1Base("/abc123"))
}
func TestHandlersv1_HandleBase(t *testing.T) {
// Setup
hdlrFunc := mockHandler.HandleBase(func() bool { return true }, types.Build{
Version: "0.1.0",
Commit: "HEAD",
BuildTime: "now",
})
// Call Handler Func
rr := httptest.NewRecorder()
hdlrFunc(rr, nil)
// Validate Status Code
if rr.Code != http.StatusOK {
t.Errorf("Expected status code to be %d, got %d", http.StatusOK, rr.Code)
}
// Validate Json Payload
expected := `{"health":true,"versions":null,"title":"Go API Template","message":"Welcome to the Go API Template Application!","Build":{"version":"0.1.0","commit":"HEAD","buildTime":"now"}}`
if rr.Body.String() != expected {
t.Errorf("Expected json to be %s, got %s", expected, rr.Body.String())
}
}

View file

@ -1,53 +0,0 @@
package v1
import (
"context"
"testing"
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/mocks"
"github.com/hay-kot/homebox/backend/internal/mocks/factories"
"github.com/hay-kot/homebox/backend/internal/types"
)
var mockHandler = &V1Controller{}
var users = []*ent.User{}
func userPool() func() {
create := []types.UserCreate{
factories.UserFactory(),
factories.UserFactory(),
factories.UserFactory(),
factories.UserFactory(),
}
userOut := []*ent.User{}
for _, user := range create {
usrOut, _ := mockHandler.svc.Admin.Create(context.Background(), user)
userOut = append(userOut, usrOut)
}
users = userOut
purge := func() {
_ = mockHandler.svc.Admin.DeleteAll(context.Background())
}
return purge
}
func TestMain(m *testing.M) {
// Set Handler Vars
repos, closeDb := mocks.GetEntRepos()
mockHandler.svc = mocks.GetMockServices(repos)
defer func() {
_ = closeDb()
}()
purge := userPool()
defer purge()
m.Run()
}

View file

@ -5,8 +5,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/types"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -21,12 +21,12 @@ and makes it a little more consistent when error handling and logging.
// from the context. If either of these fail, it will return an error. When an error
// occurs it will also write the error to the response. As such, if an error is returned
// from this function you can return immediately without writing to the response.
func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *types.UserOut, error) {
func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *repo.UserOut, error) {
uid, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
log.Err(err).Msg("failed to parse id")
server.RespondError(w, http.StatusBadRequest, err)
return uuid.Nil, nil, err
return uuid.Nil, &repo.UserOut{}, err
}
user := services.UseUserCtx(r.Context())

View file

@ -3,13 +3,25 @@ package v1
import (
"errors"
"net/http"
"time"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/types"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
type (
TokenResponse struct {
Token string `json:"token"`
ExpiresAt time.Time `json:"expiresAt"`
}
LoginForm struct {
Username string `json:"username"`
Password string `json:"password"`
}
)
// HandleAuthLogin godoc
// @Summary User Login
// @Tags Authentication
@ -18,11 +30,11 @@ import (
// @Param username formData string false "string" example(admin@admin.com)
// @Param password formData string false "string" example(admin)
// @Produce json
// @Success 200 {object} types.TokenResponse
// @Success 200 {object} TokenResponse
// @Router /v1/users/login [POST]
func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
loginForm := &types.LoginForm{}
loginForm := &LoginForm{}
if r.Header.Get("Content-Type") == server.ContentFormUrlEncoded {
err := r.ParseForm()
@ -59,9 +71,9 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
return
}
server.Respond(w, http.StatusOK, types.TokenResponse{
BearerToken: "Bearer " + newToken.Raw,
ExpiresAt: newToken.ExpiresAt,
server.Respond(w, http.StatusOK, TokenResponse{
Token: "Bearer " + newToken.Raw,
ExpiresAt: newToken.ExpiresAt,
})
}
}

View file

@ -4,8 +4,8 @@ import (
"encoding/csv"
"net/http"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/types"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -14,7 +14,7 @@ import (
// @Summary Get All Items
// @Tags Items
// @Produce json
// @Success 200 {object} server.Results{items=[]types.ItemSummary}
// @Success 200 {object} server.Results{items=[]repo.ItemSummary}
// @Router /v1/items [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
@ -34,13 +34,13 @@ func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
// @Summary Create a new item
// @Tags Items
// @Produce json
// @Param payload body types.ItemCreate true "Item Data"
// @Success 200 {object} types.ItemSummary
// @Param payload body repo.ItemCreate true "Item Data"
// @Success 200 {object} repo.ItemSummary
// @Router /v1/items [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
createData := types.ItemCreate{}
createData := repo.ItemCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("failed to decode request body")
server.RespondError(w, http.StatusInternalServerError, err)
@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Success 200 {object} types.ItemOut
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
@ -114,14 +114,14 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
// @Summary updates a item
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Param payload body types.ItemUpdate true "Item Data"
// @Success 200 {object} types.ItemOut
// @Param id path string true "Item ID"
// @Param payload body repo.ItemUpdate true "Item Data"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body := types.ItemUpdate{}
body := repo.ItemUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("failed to decode request body")
server.RespondError(w, http.StatusInternalServerError, err)

View file

@ -4,17 +4,22 @@ import (
"errors"
"fmt"
"net/http"
"path/filepath"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/ent/attachment"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/types"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
type (
ItemAttachmentToken struct {
Token string `json:"token"`
}
)
// HandleItemsImport godocs
// @Summary imports items into the database
// @Tags Items
@ -23,7 +28,7 @@ import (
// @Param file formData file true "File attachment"
// @Param type formData string true "Type of file"
// @Param name formData string true "name of the file including extension"
// @Success 200 {object} types.ItemOut
// @Success 200 {object} repo.ItemOut
// @Failure 422 {object} []server.ValidationError
// @Router /v1/items/{id}/attachments [POST]
// @Security Bearer
@ -105,7 +110,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := server.GetParam(r, "token", "")
path, err := ctrl.svc.Items.AttachmentPath(r.Context(), token)
doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), token)
if err != nil {
log.Err(err).Msg("failed to get attachment")
@ -113,9 +118,9 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
return
}
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(path)))
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", doc.Title))
w.Header().Set("Content-Type", "application/octet-stream")
http.ServeFile(w, r, path)
http.ServeFile(w, r, doc.Path)
}
}
@ -125,7 +130,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
// @Produce application/octet-stream
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Success 200 {object} types.ItemAttachmentToken
// @Success 200 {object} ItemAttachmentToken
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
@ -147,10 +152,10 @@ func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
// HandleItemAttachmentUpdate godocs
// @Summary retrieves an attachment for an item
// @Tags Items
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Param payload body types.ItemAttachmentUpdate true "Attachment Update"
// @Success 200 {object} types.ItemOut
// @Param id path string true "Item ID"
// @Param attachment_id path string true "Attachment ID"
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
// @Success 200 {object} repo.ItemOut
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc {
@ -201,7 +206,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
}
}
server.Respond(w, http.StatusOK, types.ItemAttachmentToken{Token: token})
server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token})
// Delete Attachment Handler
case http.MethodDelete:
@ -216,7 +221,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
// Update Attachment Handler
case http.MethodPut:
var attachment types.ItemAttachmentUpdate
var attachment repo.ItemAttachmentUpdate
err = server.Decode(r, &attachment)
if err != nil {
log.Err(err).Msg("failed to decode attachment")

View file

@ -4,8 +4,8 @@ import (
"net/http"
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/types"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -14,7 +14,7 @@ import (
// @Summary Get All Labels
// @Tags Labels
// @Produce json
// @Success 200 {object} server.Results{items=[]types.LabelOut}
// @Success 200 {object} server.Results{items=[]repo.LabelOut}
// @Router /v1/labels [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
@ -34,13 +34,13 @@ func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
// @Summary Create a new label
// @Tags Labels
// @Produce json
// @Param payload body types.LabelCreate true "Label Data"
// @Success 200 {object} types.LabelSummary
// @Param payload body repo.LabelCreate true "Label Data"
// @Success 200 {object} repo.LabelSummary
// @Router /v1/labels [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
createData := types.LabelCreate{}
createData := repo.LabelCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("error decoding label create data")
server.RespondError(w, http.StatusInternalServerError, err)
@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 200 {object} types.LabelOut
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
@ -122,12 +122,12 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
// @Tags Labels
// @Produce json
// @Param id path string true "Label ID"
// @Success 200 {object} types.LabelOut
// @Success 200 {object} repo.LabelOut
// @Router /v1/labels/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body := types.LabelUpdate{}
body := repo.LabelUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("error decoding label update data")
server.RespondError(w, http.StatusInternalServerError, err)

View file

@ -4,8 +4,8 @@ import (
"net/http"
"github.com/hay-kot/homebox/backend/ent"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/types"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -14,7 +14,7 @@ import (
// @Summary Get All Locations
// @Tags Locations
// @Produce json
// @Success 200 {object} server.Results{items=[]types.LocationCount}
// @Success 200 {object} server.Results{items=[]repo.LocationOutCount}
// @Router /v1/locations [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
@ -35,13 +35,13 @@ func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
// @Summary Create a new location
// @Tags Locations
// @Produce json
// @Param payload body types.LocationCreate true "Location Data"
// @Success 200 {object} types.LocationSummary
// @Param payload body repo.LocationCreate true "Location Data"
// @Success 200 {object} repo.LocationSummary
// @Router /v1/locations [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
createData := types.LocationCreate{}
createData := repo.LocationCreate{}
if err := server.Decode(r, &createData); err != nil {
log.Err(err).Msg("failed to decode location create data")
server.RespondError(w, http.StatusInternalServerError, err)
@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 200 {object} types.LocationOut
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
@ -105,12 +105,16 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
if ent.IsNotFound(err) {
log.Err(err).
Str("id", uid.String()).
Str("gid", user.GroupID.String()).
Msg("location not found")
server.RespondError(w, http.StatusNotFound, err)
return
}
log.Err(err).Msg("failed to get location")
log.Err(err).
Str("id", uid.String()).
Str("gid", user.GroupID.String()).
Msg("failed to get location")
server.RespondServerError(w)
return
}
@ -123,12 +127,12 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
// @Tags Locations
// @Produce json
// @Param id path string true "Location ID"
// @Success 200 {object} types.LocationOut
// @Success 200 {object} repo.LocationOut
// @Router /v1/locations/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body := types.LocationUpdate{}
body := repo.LocationUpdate{}
if err := server.Decode(r, &body); err != nil {
log.Err(err).Msg("failed to decode location update data")
server.RespondError(w, http.StatusInternalServerError, err)

View file

@ -4,8 +4,8 @@ import (
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/repo"
"github.com/hay-kot/homebox/backend/internal/services"
"github.com/hay-kot/homebox/backend/internal/types"
"github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
@ -14,12 +14,12 @@ import (
// @Summary Get the current user
// @Tags User
// @Produce json
// @Param payload body types.UserRegistration true "User Data"
// @Param payload body services.UserRegistration true "User Data"
// @Success 204
// @Router /v1/users/register [Post]
func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
regData := types.UserRegistration{}
regData := services.UserRegistration{}
if err := server.Decode(r, &regData); err != nil {
log.Err(err).Msg("failed to decode user registration data")
@ -29,6 +29,7 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
if err != nil {
log.Err(err).Msg("failed to register user")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
@ -41,7 +42,7 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
// @Summary Get the current user
// @Tags User
// @Produce json
// @Success 200 {object} server.Result{item=types.UserOut}
// @Success 200 {object} server.Result{item=repo.UserOut}
// @Router /v1/users/self [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
@ -62,13 +63,13 @@ func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
// @Summary Update the current user
// @Tags User
// @Produce json
// @Param payload body types.UserUpdate true "User Data"
// @Success 200 {object} server.Result{item=types.UserUpdate}
// @Param payload body repo.UserUpdate true "User Data"
// @Success 200 {object} server.Result{item=repo.UserUpdate}
// @Router /v1/users/self [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
updateData := types.UserUpdate{}
updateData := repo.UserUpdate{}
if err := server.Decode(r, &updateData); err != nil {
log.Err(err).Msg("failed to decode user update data")
server.RespondError(w, http.StatusBadRequest, err)