refactor: cleanup-api-functions (#94)

* cleanup items endpoints

* refactor group routes

* refactor labels routes

* remove old partial

* refactor location routes

* formatting

* update names

* cleanup func

* speedup test runner with disable hasher

* remove duplicate code
This commit is contained in:
Hayden 2022-10-16 18:50:44 -08:00 committed by GitHub
parent 434f1fa411
commit 18488f5b15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 259 additions and 339 deletions

View file

@ -2,14 +2,14 @@ version: "3"
env: env:
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1 HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1
UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure"
tasks: tasks:
setup: setup:
desc: Install dependencies desc: Install dependencies
cmds: cmds:
- go install github.com/swaggo/swag/cmd/swag@latest - go install github.com/swaggo/swag/cmd/swag@latest
- cd backend && go mod tidy - cd backend && go mod tidy
- cd frontend && pnpm install --shamefully-hoist - cd frontend && pnpm install --shamefully-hoist
generate: generate:
desc: | desc: |
Generates collateral files from the backend project Generates collateral files from the backend project

View file

@ -1135,27 +1135,6 @@ const docTemplate = `{
} }
} }
} }
},
"/v1/users/self/password": {
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "Update the current user's password // TODO:",
"responses": {
"204": {
"description": "No Content"
}
}
}
} }
}, },
"definitions": { "definitions": {

View file

@ -1127,27 +1127,6 @@
} }
} }
} }
},
"/v1/users/self/password": {
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "Update the current user's password // TODO:",
"responses": {
"204": {
"description": "No Content"
}
}
}
} }
}, },
"definitions": { "definitions": {

View file

@ -1138,18 +1138,6 @@ paths:
summary: Update the current user summary: Update the current user
tags: tags:
- User - User
/v1/users/self/password:
put:
produces:
- application/json
responses:
"204":
description: No Content
security:
- Bearer: []
summary: 'Update the current user''s password // TODO:'
tags:
- User
securityDefinitions: securityDefinitions:
Bearer: Bearer:
description: '"Type ''Bearer TOKEN'' to correctly set the API Key"' description: '"Type ''Bearer TOKEN'' to correctly set the API Key"'

View file

@ -63,22 +63,6 @@ func (a *app) mwAuthToken(next http.Handler) http.Handler {
}) })
} }
// mwAdminOnly is a middleware that extends the mwAuthToken middleware to only allow
// requests from superusers.
// func (a *app) mwAdminOnly(next http.Handler) http.Handler {
// mw := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// usr := services.UseUserCtx(r.Context())
// if !usr.IsSuperuser {
// server.RespondUnauthorized(w)
// return
// }
// next.ServeHTTP(w, r)
// })
// return a.mwAuthToken(mw)
// }
// mqStripTrailingSlash is a middleware that will strip trailing slashes from the request path. // mqStripTrailingSlash is a middleware that will strip trailing slashes from the request path.
func mwStripTrailingSlash(next http.Handler) http.Handler { func mwStripTrailingSlash(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -111,7 +95,6 @@ func (a *app) mwStructLogger(next http.Handler) http.Handler {
log.Info(). log.Info().
Str("id", middleware.GetReqID(r.Context())). Str("id", middleware.GetReqID(r.Context())).
Str("url", url).
Str("method", r.Method). Str("method", r.Method).
Str("remote_addr", r.RemoteAddr). Str("remote_addr", r.RemoteAddr).
Int("status", record.Status). Int("status", record.Status).

View file

@ -65,7 +65,6 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
r.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf()) r.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf())
r.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate()) r.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate())
r.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete()) r.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete())
r.Put(v1Base("/users/self/password"), v1Ctrl.HandleUserUpdatePassword())
r.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout()) r.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout())
r.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh()) r.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh())
r.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword()) r.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword())

View file

@ -33,6 +33,8 @@ type V1Controller struct {
} }
type ( type (
ReadyFunc func() bool
Build struct { Build struct {
Version string `json:"version"` Version string `json:"version"`
Commit string `json:"commit"` Commit string `json:"commit"`
@ -50,12 +52,9 @@ type (
) )
func BaseUrlFunc(prefix string) func(s string) string { func BaseUrlFunc(prefix string) func(s string) string {
v1Base := prefix + "/v1" return func(s string) string {
prefixFunc := func(s string) string { return prefix + "/v1" + s
return v1Base + s
} }
return prefixFunc
} }
func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller)) *V1Controller { func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller)) *V1Controller {
@ -71,8 +70,6 @@ func NewControllerV1(svc *services.AllServices, options ...func(*V1Controller))
return ctrl return ctrl
} }
type ReadyFunc func() bool
// HandleBase godoc // HandleBase godoc
// @Summary Retrieves the basic information about the API // @Summary Retrieves the basic information about the API
// @Tags Base // @Tags Base

View file

@ -5,30 +5,17 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/google/uuid" "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/pkgs/server" "github.com/hay-kot/homebox/backend/pkgs/server"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
/* func (ctrl *V1Controller) routeID(w http.ResponseWriter, r *http.Request) (uuid.UUID, error) {
This is where we put partial snippets/functions for actions that are commonly ID, err := uuid.Parse(chi.URLParam(r, "id"))
used within the controller class. This _hopefully_ helps with code duplication
and makes it a little more consistent when error handling and logging.
*/
// partialParseIdAndUser parses the ID from the requests URL and pulls the user
// 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, *repo.UserOut, error) {
uid, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil { if err != nil {
log.Err(err).Msg("failed to parse id") log.Err(err).Msg("failed to parse id")
server.RespondError(w, http.StatusBadRequest, err) server.RespondError(w, http.StatusBadRequest, err)
return uuid.Nil, &repo.UserOut{}, err return uuid.Nil, err
} }
user := services.UseUserCtx(r.Context()) return ID, nil
return uid, user, nil
} }

View file

@ -32,20 +32,7 @@ type (
// @Router /v1/groups [Get] // @Router /v1/groups [Get]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc { func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return ctrl.handleGroupGeneral()
ctx := services.NewContext(r.Context())
group, err := ctrl.svc.Group.Get(ctx)
if err != nil {
log.Err(err).Msg("failed to get group")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower caseÍ
server.Respond(w, http.StatusOK, group)
}
} }
// HandleGroupUpdate godoc // HandleGroupUpdate godoc
@ -57,24 +44,41 @@ func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc {
// @Router /v1/groups [Put] // @Router /v1/groups [Put]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleGroupUpdate() http.HandlerFunc { func (ctrl *V1Controller) HandleGroupUpdate() http.HandlerFunc {
return ctrl.handleGroupGeneral()
}
func (ctrl *V1Controller) handleGroupGeneral() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
data := repo.GroupUpdate{}
if err := server.Decode(r, &data); err != nil {
server.RespondError(w, http.StatusBadRequest, err)
return
}
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
group, err := ctrl.svc.Group.UpdateGroup(ctx, data) switch r.Method {
if err != nil { case http.MethodGet:
log.Err(err).Msg("failed to update group") group, err := ctrl.svc.Group.Get(ctx)
server.RespondError(w, http.StatusInternalServerError, err) if err != nil {
return log.Err(err).Msg("failed to get group")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower caseÍ
server.Respond(w, http.StatusOK, group)
case http.MethodPut:
data := repo.GroupUpdate{}
if err := server.Decode(r, &data); err != nil {
server.RespondError(w, http.StatusBadRequest, err)
return
}
group, err := ctrl.svc.Group.UpdateGroup(ctx, data)
if err != nil {
log.Err(err).Msg("failed to update group")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower case
server.Respond(w, http.StatusOK, group)
} }
group.Currency = strings.ToUpper(group.Currency) // TODO: Hack to fix the currency enums being lower case
server.Respond(w, http.StatusOK, group)
} }
} }
@ -89,7 +93,6 @@ func (ctrl *V1Controller) HandleGroupUpdate() http.HandlerFunc {
func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc { func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
data := GroupInvitationCreate{} data := GroupInvitationCreate{}
if err := server.Decode(r, &data); err != nil { if err := server.Decode(r, &data); err != nil {
log.Err(err).Msg("failed to decode user registration data") log.Err(err).Msg("failed to decode user registration data")
server.RespondError(w, http.StatusBadRequest, err) server.RespondError(w, http.StatusBadRequest, err)

View file

@ -13,41 +13,6 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
func uuidList(params url.Values, key string) []uuid.UUID {
var ids []uuid.UUID
for _, id := range params[key] {
uid, err := uuid.Parse(id)
if err != nil {
continue
}
ids = append(ids, uid)
}
return ids
}
func intOrNegativeOne(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
return -1
}
return i
}
func extractQuery(r *http.Request) repo.ItemQuery {
params := r.URL.Query()
page := intOrNegativeOne(params.Get("page"))
perPage := intOrNegativeOne(params.Get("perPage"))
return repo.ItemQuery{
Page: page,
PageSize: perPage,
Search: params.Get("q"),
LocationIDs: uuidList(params, "locations"),
LabelIDs: uuidList(params, "labels"),
}
}
// HandleItemsGetAll godoc // HandleItemsGetAll godoc
// @Summary Get All Items // @Summary Get All Items
// @Tags Items // @Tags Items
@ -61,6 +26,38 @@ func extractQuery(r *http.Request) repo.ItemQuery {
// @Router /v1/items [GET] // @Router /v1/items [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc { func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
uuidList := func(params url.Values, key string) []uuid.UUID {
var ids []uuid.UUID
for _, id := range params[key] {
uid, err := uuid.Parse(id)
if err != nil {
continue
}
ids = append(ids, uid)
}
return ids
}
intOrNegativeOne := func(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
return -1
}
return i
}
extractQuery := func(r *http.Request) repo.ItemQuery {
params := r.URL.Query()
return repo.ItemQuery{
Page: intOrNegativeOne(params.Get("page")),
PageSize: intOrNegativeOne(params.Get("perPage")),
Search: params.Get("q"),
LocationIDs: uuidList(params, "locations"),
LabelIDs: uuidList(params, "labels"),
}
}
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := services.NewContext(r.Context()) ctx := services.NewContext(r.Context())
items, err := ctrl.svc.Items.Query(ctx, extractQuery(r)) items, err := ctrl.svc.Items.Query(ctx, extractQuery(r))
@ -102,31 +99,6 @@ func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
} }
} }
// HandleItemDelete godocs
// @Summary deletes a item
// @Tags Items
// @Produce json
// @Param id path string true "Item ID"
// @Success 204
// @Router /v1/items/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
err = ctrl.svc.Items.Delete(r.Context(), user.GroupID, uid)
if err != nil {
log.Err(err).Msg("failed to delete item")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
}
// HandleItemGet godocs // HandleItemGet godocs
// @Summary Gets a item and fields // @Summary Gets a item and fields
// @Tags Items // @Tags Items
@ -136,20 +108,19 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
// @Router /v1/items/{id} [GET] // @Router /v1/items/{id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc { func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return ctrl.handleItemsGeneral()
uid, user, err := ctrl.partialParseIdAndUser(w, r) }
if err != nil {
return
}
items, err := ctrl.svc.Items.GetOne(r.Context(), user.GroupID, uid) // HandleItemDelete godocs
if err != nil { // @Summary deletes a item
log.Err(err).Msg("failed to get item") // @Tags Items
server.RespondServerError(w) // @Produce json
return // @Param id path string true "Item ID"
} // @Success 204
server.Respond(w, http.StatusOK, items) // @Router /v1/items/{id} [DELETE]
} // @Security Bearer
func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
return ctrl.handleItemsGeneral()
} }
// HandleItemUpdate godocs // HandleItemUpdate godocs
@ -162,26 +133,53 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
// @Router /v1/items/{id} [PUT] // @Router /v1/items/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc { func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
return ctrl.handleItemsGeneral()
}
func (ctrl *V1Controller) handleItemsGeneral() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
body := repo.ItemUpdate{} ctx := services.NewContext(r.Context())
if err := server.Decode(r, &body); err != nil { ID, err := ctrl.routeID(w, r)
log.Err(err).Msg("failed to decode request body")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil { if err != nil {
return return
} }
body.ID = uid switch r.Method {
result, err := ctrl.svc.Items.Update(r.Context(), user.GroupID, body) case http.MethodGet:
if err != nil { items, err := ctrl.svc.Items.GetOne(r.Context(), ctx.GID, ID)
log.Err(err).Msg("failed to update item") if err != nil {
server.RespondServerError(w) log.Err(err).Msg("failed to get item")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, items)
return return
case http.MethodDelete:
err = ctrl.svc.Items.Delete(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to delete item")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
return
case http.MethodPut:
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)
return
}
body.ID = ID
result, err := ctrl.svc.Items.Update(r.Context(), ctx.GID, body)
if err != nil {
log.Err(err).Msg("failed to update item")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, result)
} }
server.Respond(w, http.StatusOK, result)
} }
} }

View file

@ -72,7 +72,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
attachmentType = attachment.TypeAttachment.String() attachmentType = attachment.TypeAttachment.String()
} }
id, _, err := ctrl.partialParseIdAndUser(w, r) id, err := ctrl.routeID(w, r)
if err != nil { if err != nil {
return return
} }
@ -163,7 +163,7 @@ func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc {
} }
func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) { func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r *http.Request) {
uid, user, err := ctrl.partialParseIdAndUser(w, r) ID, err := ctrl.routeID(w, r)
if err != nil { if err != nil {
return return
} }
@ -181,7 +181,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
// Token Handler // Token Handler
case http.MethodGet: case http.MethodGet:
token, err := ctrl.svc.Items.AttachmentToken(ctx, uid, attachmentId) token, err := ctrl.svc.Items.AttachmentToken(ctx, ID, attachmentId)
if err != nil { if err != nil {
switch err { switch err {
case services.ErrNotFound: case services.ErrNotFound:
@ -210,7 +210,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
// Delete Attachment Handler // Delete Attachment Handler
case http.MethodDelete: case http.MethodDelete:
err = ctrl.svc.Items.AttachmentDelete(r.Context(), user.GroupID, uid, attachmentId) err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentId)
if err != nil { if err != nil {
log.Err(err).Msg("failed to delete attachment") log.Err(err).Msg("failed to delete attachment")
server.RespondServerError(w) server.RespondServerError(w)
@ -230,7 +230,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
} }
attachment.ID = attachmentId attachment.ID = attachmentId
val, err := ctrl.svc.Items.AttachmentUpdate(ctx, uid, &attachment) val, err := ctrl.svc.Items.AttachmentUpdate(ctx, ID, &attachment)
if err != nil { if err != nil {
log.Err(err).Msg("failed to delete attachment") log.Err(err).Msg("failed to delete attachment")
server.RespondServerError(w) server.RespondServerError(w)

View file

@ -56,7 +56,6 @@ func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
} }
server.Respond(w, http.StatusCreated, label) server.Respond(w, http.StatusCreated, label)
} }
} }
@ -69,20 +68,7 @@ func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
// @Router /v1/labels/{id} [DELETE] // @Router /v1/labels/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc { func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return ctrl.handleLabelsGeneral()
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
err = ctrl.svc.Labels.Delete(r.Context(), user.GroupID, uid)
if err != nil {
log.Err(err).Msg("error deleting label")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
} }
// HandleLabelGet godocs // HandleLabelGet godocs
@ -94,27 +80,7 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
// @Router /v1/labels/{id} [GET] // @Router /v1/labels/{id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc { func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return ctrl.handleLabelsGeneral()
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
labels, err := ctrl.svc.Labels.Get(r.Context(), user.GroupID, uid)
if err != nil {
if ent.IsNotFound(err) {
log.Err(err).
Str("id", uid.String()).
Msg("label not found")
server.RespondError(w, http.StatusNotFound, err)
return
}
log.Err(err).Msg("error getting label")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, labels)
}
} }
// HandleLabelUpdate godocs // HandleLabelUpdate godocs
@ -126,25 +92,59 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
// @Router /v1/labels/{id} [PUT] // @Router /v1/labels/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc { func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
return ctrl.handleLabelsGeneral()
}
func (ctrl *V1Controller) handleLabelsGeneral() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
body := repo.LabelUpdate{} ctx := services.NewContext(r.Context())
if err := server.Decode(r, &body); err != nil { ID, err := ctrl.routeID(w, r)
log.Err(err).Msg("error decoding label update data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil { if err != nil {
return return
} }
body.ID = uid switch r.Method {
result, err := ctrl.svc.Labels.Update(r.Context(), user.GroupID, body) case http.MethodGet:
if err != nil { labels, err := ctrl.svc.Labels.Get(r.Context(), ctx.GID, ID)
log.Err(err).Msg("error updating label") if err != nil {
server.RespondServerError(w) if ent.IsNotFound(err) {
return log.Err(err).
Str("id", ID.String()).
Msg("label not found")
server.RespondError(w, http.StatusNotFound, err)
return
}
log.Err(err).Msg("error getting label")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, labels)
case http.MethodDelete:
err = ctrl.svc.Labels.Delete(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("error deleting label")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
case http.MethodPut:
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)
return
}
body.ID = ID
result, err := ctrl.svc.Labels.Update(r.Context(), ctx.GID, body)
if err != nil {
log.Err(err).Msg("error updating label")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, result)
} }
server.Respond(w, http.StatusOK, result)
} }
} }

View file

@ -69,20 +69,7 @@ func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
// @Router /v1/locations/{id} [DELETE] // @Router /v1/locations/{id} [DELETE]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc { func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return ctrl.handleLocationGeneral()
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
err = ctrl.svc.Location.Delete(r.Context(), user.GroupID, uid)
if err != nil {
log.Err(err).Msg("failed to delete location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
}
} }
// HandleLocationGet godocs // HandleLocationGet godocs
@ -94,32 +81,7 @@ func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
// @Router /v1/locations/{id} [GET] // @Router /v1/locations/{id} [GET]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc { func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return ctrl.handleLocationGeneral()
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil {
return
}
location, err := ctrl.svc.Location.GetOne(r.Context(), user.GroupID, uid)
if err != nil {
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).
Str("id", uid.String()).
Str("gid", user.GroupID.String()).
Msg("failed to get location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, location)
}
} }
// HandleLocationUpdate godocs // HandleLocationUpdate godocs
@ -131,27 +93,61 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
// @Router /v1/locations/{id} [PUT] // @Router /v1/locations/{id} [PUT]
// @Security Bearer // @Security Bearer
func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc { func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
return ctrl.handleLocationGeneral()
}
func (ctrl *V1Controller) handleLocationGeneral() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
body := repo.LocationUpdate{} ctx := services.NewContext(r.Context())
if err := server.Decode(r, &body); err != nil { ID, err := ctrl.routeID(w, r)
log.Err(err).Msg("failed to decode location update data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
uid, user, err := ctrl.partialParseIdAndUser(w, r)
if err != nil { if err != nil {
return return
} }
body.ID = uid switch r.Method {
case http.MethodGet:
location, err := ctrl.svc.Location.GetOne(r.Context(), ctx.GID, ID)
if err != nil {
l := log.Err(err).
Str("ID", ID.String()).
Str("GID", ctx.GID.String())
result, err := ctrl.svc.Location.Update(r.Context(), user.GroupID, body) if ent.IsNotFound(err) {
if err != nil { l.Msg("location not found")
log.Err(err).Msg("failed to update location") server.RespondError(w, http.StatusNotFound, err)
server.RespondServerError(w) return
return }
l.Msg("failed to get location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, location)
case http.MethodPut:
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)
return
}
body.ID = ID
result, err := ctrl.svc.Location.Update(r.Context(), ctx.GID, body)
if err != nil {
log.Err(err).Msg("failed to update location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusOK, result)
case http.MethodDelete:
err = ctrl.svc.Location.Delete(r.Context(), ctx.GID, ID)
if err != nil {
log.Err(err).Msg("failed to delete location")
server.RespondServerError(w)
return
}
server.Respond(w, http.StatusNoContent, nil)
} }
server.Respond(w, http.StatusOK, result)
} }
} }

View file

@ -85,7 +85,6 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData) newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData)
if err != nil { if err != nil {
server.RespondError(w, http.StatusInternalServerError, err) server.RespondError(w, http.StatusInternalServerError, err)
return return
} }
@ -94,18 +93,6 @@ func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
} }
} }
// HandleUserUpdatePassword godoc
// @Summary Update the current user's password // TODO:
// @Tags User
// @Produce json
// @Success 204
// @Router /v1/users/self/password [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleUserUpdatePassword() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
}
}
// HandleUserSelfDelete godoc // HandleUserSelfDelete godoc
// @Summary Deletes the user account // @Summary Deletes the user account
// @Tags User // @Tags User

View file

@ -1,13 +1,37 @@
package hasher package hasher
import "golang.org/x/crypto/bcrypt" import (
"fmt"
"os"
"golang.org/x/crypto/bcrypt"
)
var enabled = true
func init() {
disableHas := os.Getenv("UNSAFE_DISABLE_PASSWORD_PROJECTION") == "yes_i_am_sure"
if disableHas {
fmt.Println("WARNING: Password projection is disabled. This is unsafe in production.")
enabled = false
}
}
func HashPassword(password string) (string, error) { func HashPassword(password string) (string, error) {
if !enabled {
return password, nil
}
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err return string(bytes), err
} }
func CheckPasswordHash(password, hash string) bool { func CheckPasswordHash(password, hash string) bool {
if !enabled {
return password == hash
}
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil return err == nil
} }