From de419dc37dd9fcc7578f8b55c230428e5d452b3e Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sat, 3 Dec 2022 10:55:00 -0900 Subject: [PATCH] feat: auth-roles, image-gallery, click-to-open (#166) * schema changes * db generate * db migration * add role based middleware * implement attachment token access * generate docs * implement role based auth * replace attachment specific tokens with gen token * run linter * cleanup temporary token implementation --- backend/app/api/handlers/v1/v1_ctrl_auth.go | 10 +- .../handlers/v1/v1_ctrl_items_attachments.go | 68 +- backend/app/api/middleware.go | 77 ++- backend/app/api/routes.go | 75 ++- backend/app/api/static/docs/docs.go | 3 + backend/app/api/static/docs/swagger.json | 3 + backend/app/api/static/docs/swagger.yaml | 2 + backend/internal/core/services/all.go | 1 - .../internal/core/services/service_items.go | 3 - .../services/service_items_attachments.go | 58 +- .../internal/core/services/service_user.go | 45 +- backend/internal/data/ent/authroles.go | 141 ++++ .../internal/data/ent/authroles/authroles.go | 81 +++ backend/internal/data/ent/authroles/where.go | 176 +++++ backend/internal/data/ent/authroles_create.go | 286 ++++++++ backend/internal/data/ent/authroles_delete.go | 115 ++++ backend/internal/data/ent/authroles_query.go | 633 ++++++++++++++++++ backend/internal/data/ent/authroles_update.go | 433 ++++++++++++ backend/internal/data/ent/authtokens.go | 23 +- .../data/ent/authtokens/authtokens.go | 9 + backend/internal/data/ent/authtokens/where.go | 28 + .../internal/data/ent/authtokens_create.go | 39 ++ backend/internal/data/ent/authtokens_query.go | 74 +- .../internal/data/ent/authtokens_update.go | 121 ++++ backend/internal/data/ent/client.go | 129 ++++ backend/internal/data/ent/config.go | 1 + backend/internal/data/ent/ent.go | 2 + backend/internal/data/ent/hook/hook.go | 13 + backend/internal/data/ent/migrate/schema.go | 22 + backend/internal/data/ent/mutation.go | 445 +++++++++++- .../internal/data/ent/predicate/predicate.go | 3 + backend/internal/data/ent/runtime.go | 2 + .../internal/data/ent/schema/auth_roles.go | 34 + .../internal/data/ent/schema/auth_tokens.go | 2 + backend/internal/data/ent/tx.go | 3 + .../20221203053132_add_token_roles.sql | 4 + .../data/migrations/migrations/atlas.sum | 3 +- backend/internal/data/repo/repo_tokens.go | 40 +- backend/pkgs/set/set.go | 6 + frontend/components/Item/AttachmentsList.vue | 27 +- frontend/composables/use-api.ts | 2 +- frontend/lib/api/base/base-api.ts | 14 +- frontend/lib/api/classes/items.ts | 21 +- frontend/lib/api/types/data-contracts.ts | 1 + frontend/lib/api/user.ts | 4 +- frontend/pages/index.vue | 1 + frontend/pages/item/[id]/index.vue | 86 ++- frontend/stores/auth.ts | 2 + 48 files changed, 3127 insertions(+), 244 deletions(-) create mode 100644 backend/internal/data/ent/authroles.go create mode 100644 backend/internal/data/ent/authroles/authroles.go create mode 100644 backend/internal/data/ent/authroles/where.go create mode 100644 backend/internal/data/ent/authroles_create.go create mode 100644 backend/internal/data/ent/authroles_delete.go create mode 100644 backend/internal/data/ent/authroles_query.go create mode 100644 backend/internal/data/ent/authroles_update.go create mode 100644 backend/internal/data/ent/schema/auth_roles.go create mode 100644 backend/internal/data/migrations/migrations/20221203053132_add_token_roles.sql diff --git a/backend/app/api/handlers/v1/v1_ctrl_auth.go b/backend/app/api/handlers/v1/v1_ctrl_auth.go index b005a9d..24b1654 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_auth.go +++ b/backend/app/api/handlers/v1/v1_ctrl_auth.go @@ -13,8 +13,9 @@ import ( type ( TokenResponse struct { - Token string `json:"token"` - ExpiresAt time.Time `json:"expiresAt"` + Token string `json:"token"` + ExpiresAt time.Time `json:"expiresAt"` + AttachmentToken string `json:"attachmentToken"` } LoginForm struct { @@ -76,8 +77,9 @@ func (ctrl *V1Controller) HandleAuthLogin() server.HandlerFunc { } return server.Respond(w, http.StatusOK, TokenResponse{ - Token: "Bearer " + newToken.Raw, - ExpiresAt: newToken.ExpiresAt, + Token: "Bearer " + newToken.Raw, + ExpiresAt: newToken.ExpiresAt, + AttachmentToken: newToken.AttachmentToken, }) } } diff --git a/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go b/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go index 4eae453..033f683 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go @@ -2,10 +2,7 @@ package v1 import ( "errors" - "fmt" "net/http" - "path/filepath" - "strings" "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment" @@ -99,47 +96,12 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() server.HandlerFunc { // @Summary retrieves an attachment for an item // @Tags Items Attachments // @Produce application/octet-stream -// @Param id path string true "Item ID" -// @Param token query string true "Attachment token" -// @Success 200 -// @Router /v1/items/{id}/attachments/download [GET] -// @Security Bearer -func (ctrl *V1Controller) HandleItemAttachmentDownload() server.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - token := server.GetParam(r, "token", "") - - doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), token) - - if err != nil { - log.Err(err).Msg("failed to get attachment") - return validate.NewRequestError(err, http.StatusInternalServerError) - } - - ext := filepath.Ext(doc.Path) - - title := doc.Title - - if !strings.HasSuffix(doc.Title, ext) { - title = doc.Title + ext - } - - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", title)) - w.Header().Set("Content-Type", "application/octet-stream") - http.ServeFile(w, r, doc.Path) - return nil - } -} - -// HandleItemAttachmentToken godocs -// @Summary retrieves an attachment for an item -// @Tags Items Attachments -// @Produce application/octet-stream // @Param id path string true "Item ID" // @Param attachment_id path string true "Attachment ID" // @Success 200 {object} ItemAttachmentToken // @Router /v1/items/{id}/attachments/{attachment_id} [GET] // @Security Bearer -func (ctrl *V1Controller) HandleItemAttachmentToken() server.HandlerFunc { +func (ctrl *V1Controller) HandleItemAttachmentGet() server.HandlerFunc { return ctrl.handleItemAttachmentsHandler } @@ -181,33 +143,15 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r ctx := services.NewContext(r.Context()) switch r.Method { - // Token Handler case http.MethodGet: - token, err := ctrl.svc.Items.AttachmentToken(ctx, ID, attachmentID) + doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), attachmentID) if err != nil { - switch err { - case services.ErrNotFound: - log.Err(err). - Str("id", attachmentID.String()). - Msg("failed to find attachment with id") - - return validate.NewRequestError(err, http.StatusNotFound) - - case services.ErrFileNotFound: - log.Err(err). - Str("id", attachmentID.String()). - Msg("failed to find file path for attachment with id") - log.Warn().Msg("attachment with no file path removed from database") - - return validate.NewRequestError(err, http.StatusNotFound) - - default: - log.Err(err).Msg("failed to get attachment") - return validate.NewRequestError(err, http.StatusInternalServerError) - } + log.Err(err).Msg("failed to get attachment path") + return validate.NewRequestError(err, http.StatusInternalServerError) } - return server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token}) + http.ServeFile(w, r, doc.Path) + return nil // Delete Attachment Handler case http.MethodDelete: diff --git a/backend/app/api/middleware.go b/backend/app/api/middleware.go index 505ba40..8baddfb 100644 --- a/backend/app/api/middleware.go +++ b/backend/app/api/middleware.go @@ -1,6 +1,7 @@ package main import ( + "context" "errors" "net/http" "strings" @@ -10,17 +11,87 @@ import ( "github.com/hay-kot/homebox/backend/pkgs/server" ) +type tokenHasKey struct { + key string +} + +var ( + hashedToken = tokenHasKey{key: "hashedToken"} +) + +type RoleMode int + +const ( + RoleModeOr RoleMode = 0 + RoleModeAnd RoleMode = 1 +) + +// mwRoles is a middleware that will validate the required roles are met. All roles +// are required to be met for the request to be allowed. If the user does not have +// the required roles, a 403 Forbidden will be returned. +// +// WARNING: This middleware _MUST_ be called after mwAuthToken or else it will panic +func (a *app) mwRoles(rm RoleMode, required ...string) server.Middleware { + return func(next server.Handler) server.Handler { + return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + ctx := r.Context() + + maybeToken := ctx.Value(hashedToken) + if maybeToken == nil { + panic("mwRoles: token not found in context, you must call mwAuthToken before mwRoles") + } + + token := maybeToken.(string) + + roles, err := a.repos.AuthTokens.GetRoles(r.Context(), token) + if err != nil { + return err + } + + outer: + switch rm { + case RoleModeOr: + for _, role := range required { + if roles.Contains(role) { + break outer + } + } + return validate.NewRequestError(errors.New("Forbidden"), http.StatusForbidden) + case RoleModeAnd: + for _, req := range required { + if !roles.Contains(req) { + return validate.NewRequestError(errors.New("Unauthorized"), http.StatusForbidden) + } + } + } + + return next.ServeHTTP(w, r) + }) + } +} + // mwAuthToken is a middleware that will check the database for a stateful token -// and attach it to the request context with the user, or return a 401 if it doesn't exist. +// and attach it's user to the request context, or return an appropriate error. +// Authorization support is by token via Headers or Query Parameter +// +// Example: +// - header = "Bearer 1234567890" +// - query = "?access_token=1234567890" func (a *app) mwAuthToken(next server.Handler) server.Handler { return server.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { requestToken := r.Header.Get("Authorization") - if requestToken == "" { - return validate.NewRequestError(errors.New("Authorization header is required"), http.StatusUnauthorized) + // check for query param + requestToken = r.URL.Query().Get("access_token") + if requestToken == "" { + return validate.NewRequestError(errors.New("Authorization header or query is required"), http.StatusUnauthorized) + } } requestToken = strings.TrimPrefix(requestToken, "Bearer ") + + r = r.WithContext(context.WithValue(r.Context(), hashedToken, requestToken)) + usr, err := a.services.User.GetSelf(r.Context(), requestToken) // Check the database for the token diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index cab1a14..e5a7948 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -13,6 +13,7 @@ import ( "github.com/hay-kot/homebox/backend/app/api/handlers/debughandlers" v1 "github.com/hay-kot/homebox/backend/app/api/handlers/v1" _ "github.com/hay-kot/homebox/backend/app/api/static/docs" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/pkgs/server" httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware @@ -64,49 +65,55 @@ func (a *app) mountRoutes(repos *repo.AllRepos) { a.server.Post(v1Base("/users/register"), v1Ctrl.HandleUserRegistration()) a.server.Post(v1Base("/users/login"), v1Ctrl.HandleAuthLogin()) - // Attachment download URl needs a `token` query param to be passed in the request. - // and also needs to be outside of the `auth` middleware. - a.server.Get(v1Base("/items/{id}/attachments/download"), v1Ctrl.HandleItemAttachmentDownload()) + userMW := []server.Middleware{ + a.mwAuthToken, + a.mwRoles(RoleModeOr, authroles.RoleUser.String()), + } - a.server.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf(), a.mwAuthToken) - a.server.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate(), a.mwAuthToken) - a.server.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete(), a.mwAuthToken) - a.server.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout(), a.mwAuthToken) - a.server.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh(), a.mwAuthToken) - a.server.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword(), a.mwAuthToken) + a.server.Get(v1Base("/users/self"), v1Ctrl.HandleUserSelf(), userMW...) + a.server.Put(v1Base("/users/self"), v1Ctrl.HandleUserSelfUpdate(), userMW...) + a.server.Delete(v1Base("/users/self"), v1Ctrl.HandleUserSelfDelete(), userMW...) + a.server.Post(v1Base("/users/logout"), v1Ctrl.HandleAuthLogout(), userMW...) + a.server.Get(v1Base("/users/refresh"), v1Ctrl.HandleAuthRefresh(), userMW...) + a.server.Put(v1Base("/users/self/change-password"), v1Ctrl.HandleUserSelfChangePassword(), userMW...) - a.server.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate(), a.mwAuthToken) - a.server.Get(v1Base("/groups/statistics"), v1Ctrl.HandleGroupStatistics(), a.mwAuthToken) + a.server.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate(), userMW...) + a.server.Get(v1Base("/groups/statistics"), v1Ctrl.HandleGroupStatistics(), userMW...) // TODO: I don't like /groups being the URL for users - a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), a.mwAuthToken) - a.server.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate(), a.mwAuthToken) + a.server.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet(), userMW...) + a.server.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate(), userMW...) - a.server.Post(v1Base("/actions/ensure-asset-ids"), v1Ctrl.HandleEnsureAssetID(), a.mwAuthToken) + a.server.Post(v1Base("/actions/ensure-asset-ids"), v1Ctrl.HandleEnsureAssetID(), userMW...) - a.server.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll(), a.mwAuthToken) - a.server.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate(), a.mwAuthToken) - a.server.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet(), a.mwAuthToken) - a.server.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate(), a.mwAuthToken) - a.server.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete(), a.mwAuthToken) + a.server.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll(), userMW...) + a.server.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate(), userMW...) + a.server.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet(), userMW...) + a.server.Put(v1Base("/locations/{id}"), v1Ctrl.HandleLocationUpdate(), userMW...) + a.server.Delete(v1Base("/locations/{id}"), v1Ctrl.HandleLocationDelete(), userMW...) - a.server.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll(), a.mwAuthToken) - a.server.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate(), a.mwAuthToken) - a.server.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet(), a.mwAuthToken) - a.server.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate(), a.mwAuthToken) - a.server.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete(), a.mwAuthToken) + a.server.Get(v1Base("/labels"), v1Ctrl.HandleLabelsGetAll(), userMW...) + a.server.Post(v1Base("/labels"), v1Ctrl.HandleLabelsCreate(), userMW...) + a.server.Get(v1Base("/labels/{id}"), v1Ctrl.HandleLabelGet(), userMW...) + a.server.Put(v1Base("/labels/{id}"), v1Ctrl.HandleLabelUpdate(), userMW...) + a.server.Delete(v1Base("/labels/{id}"), v1Ctrl.HandleLabelDelete(), userMW...) - a.server.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll(), a.mwAuthToken) - a.server.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport(), a.mwAuthToken) - a.server.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate(), a.mwAuthToken) - a.server.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet(), a.mwAuthToken) - a.server.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate(), a.mwAuthToken) - a.server.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete(), a.mwAuthToken) + a.server.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll(), userMW...) + a.server.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport(), userMW...) + a.server.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate(), userMW...) + a.server.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet(), userMW...) + a.server.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate(), userMW...) + a.server.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete(), userMW...) - a.server.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate(), a.mwAuthToken) - a.server.Get(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentToken(), a.mwAuthToken) - a.server.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate(), a.mwAuthToken) - a.server.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete(), a.mwAuthToken) + a.server.Post(v1Base("/items/{id}/attachments"), v1Ctrl.HandleItemAttachmentCreate(), userMW...) + a.server.Put(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentUpdate(), userMW...) + a.server.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), v1Ctrl.HandleItemAttachmentDelete(), userMW...) + + a.server.Get( + v1Base("/items/{id}/attachments/{attachment_id}"), + v1Ctrl.HandleItemAttachmentGet(), + a.mwAuthToken, a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()), + ) a.server.NotFound(notFoundHandler()) } diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index 6e02411..6e8cebd 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -1966,6 +1966,9 @@ const docTemplate = `{ "v1.TokenResponse": { "type": "object", "properties": { + "attachmentToken": { + "type": "string" + }, "expiresAt": { "type": "string" }, diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 3be73bd..f09cffa 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -1958,6 +1958,9 @@ "v1.TokenResponse": { "type": "object", "properties": { + "attachmentToken": { + "type": "string" + }, "expiresAt": { "type": "string" }, diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index 72563b9..1e3b528 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -510,6 +510,8 @@ definitions: type: object v1.TokenResponse: properties: + attachmentToken: + type: string expiresAt: type: string token: diff --git a/backend/internal/core/services/all.go b/backend/internal/core/services/all.go index 25e406e..43deb52 100644 --- a/backend/internal/core/services/all.go +++ b/backend/internal/core/services/all.go @@ -38,7 +38,6 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices { Group: &GroupService{repos}, Items: &ItemService{ repo: repos, - at: attachmentTokens{}, autoIncrementAssetID: options.autoIncrementAssetID, }, } diff --git a/backend/internal/core/services/service_items.go b/backend/internal/core/services/service_items.go index 5055c67..df37e67 100644 --- a/backend/internal/core/services/service_items.go +++ b/backend/internal/core/services/service_items.go @@ -18,9 +18,6 @@ type ItemService struct { repo *repo.AllRepos filepath string - // at is a map of tokens to attachment IDs. This is used to store the attachment ID - // for issued URLs - at attachmentTokens autoIncrementAssetID bool } diff --git a/backend/internal/core/services/service_items_attachments.go b/backend/internal/core/services/service_items_attachments.go index b5df5f8..4a7b197 100644 --- a/backend/internal/core/services/service_items_attachments.go +++ b/backend/internal/core/services/service_items_attachments.go @@ -4,71 +4,15 @@ import ( "context" "io" "os" - "time" "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/data/ent" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/repo" - "github.com/hay-kot/homebox/backend/pkgs/hasher" "github.com/rs/zerolog/log" ) -// TODO: this isn't a scalable solution, tokens should be stored in the database -type attachmentTokens map[string]uuid.UUID - -func (at attachmentTokens) Add(token string, id uuid.UUID) { - at[token] = id - - log.Debug().Str("token", token).Str("uuid", id.String()).Msg("added token") - - go func() { - ch := time.After(1 * time.Minute) - <-ch - at.Delete(token) - log.Debug().Str("token", token).Msg("deleted token") - }() -} - -func (at attachmentTokens) Get(token string) (uuid.UUID, bool) { - id, ok := at[token] - return id, ok -} - -func (at attachmentTokens) Delete(token string) { - delete(at, token) -} - -func (svc *ItemService) AttachmentToken(ctx Context, itemId, attachmentId uuid.UUID) (string, error) { - _, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId) - if err != nil { - return "", err - } - - token := hasher.GenerateToken() - - // Ensure that the file exists - attachment, err := svc.repo.Attachments.Get(ctx, attachmentId) - if err != nil { - return "", err - } - - if _, err := os.Stat(attachment.Edges.Document.Path); os.IsNotExist(err) { - _ = svc.AttachmentDelete(ctx, ctx.GID, itemId, attachmentId) - return "", ErrNotFound - } - - svc.at.Add(token.Raw, attachmentId) - - return token.Raw, nil -} - -func (svc *ItemService) AttachmentPath(ctx context.Context, token string) (*ent.Document, error) { - attachmentId, ok := svc.at.Get(token) - if !ok { - return nil, ErrNotFound - } - +func (svc *ItemService) AttachmentPath(ctx context.Context, attachmentId uuid.UUID) (*ent.Document, error) { attachment, err := svc.repo.Attachments.Get(ctx, attachmentId) if err != nil { return nil, err diff --git a/backend/internal/core/services/service_user.go b/backend/internal/core/services/service_user.go index e3d8f8a..6d3bc6e 100644 --- a/backend/internal/core/services/service_user.go +++ b/backend/internal/core/services/service_user.go @@ -6,6 +6,7 @@ import ( "time" "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/pkgs/hasher" "github.com/rs/zerolog/log" @@ -30,8 +31,9 @@ type ( Password string `json:"password"` } UserAuthTokenDetail struct { - Raw string `json:"raw"` - ExpiresAt time.Time `json:"expiresAt"` + Raw string `json:"raw"` + AttachmentToken string `json:"attachmentToken"` + ExpiresAt time.Time `json:"expiresAt"` } LoginForm struct { Username string `json:"username"` @@ -131,16 +133,37 @@ func (svc *UserService) UpdateSelf(ctx context.Context, ID uuid.UUID, data repo. // ============================================================================ // User Authentication -func (svc *UserService) createToken(ctx context.Context, userId uuid.UUID) (UserAuthTokenDetail, error) { - newToken := hasher.GenerateToken() +func (svc *UserService) createSessionToken(ctx context.Context, userId uuid.UUID) (UserAuthTokenDetail, error) { - created, err := svc.repos.AuthTokens.CreateToken(ctx, repo.UserAuthTokenCreate{ + attachmentToken := hasher.GenerateToken() + attachmentData := repo.UserAuthTokenCreate{ UserID: userId, - TokenHash: newToken.Hash, + TokenHash: attachmentToken.Hash, ExpiresAt: time.Now().Add(oneWeek), - }) + } - return UserAuthTokenDetail{Raw: newToken.Raw, ExpiresAt: created.ExpiresAt}, err + _, err := svc.repos.AuthTokens.CreateToken(ctx, attachmentData, authroles.RoleAttachments) + if err != nil { + return UserAuthTokenDetail{}, err + } + + userToken := hasher.GenerateToken() + data := repo.UserAuthTokenCreate{ + UserID: userId, + TokenHash: userToken.Hash, + ExpiresAt: time.Now().Add(oneWeek), + } + + created, err := svc.repos.AuthTokens.CreateToken(ctx, data, authroles.RoleUser) + if err != nil { + return UserAuthTokenDetail{}, err + } + + return UserAuthTokenDetail{ + Raw: userToken.Raw, + ExpiresAt: created.ExpiresAt, + AttachmentToken: attachmentToken.Raw, + }, nil } func (svc *UserService) Login(ctx context.Context, username, password string) (UserAuthTokenDetail, error) { @@ -156,7 +179,7 @@ func (svc *UserService) Login(ctx context.Context, username, password string) (U return UserAuthTokenDetail{}, ErrorInvalidLogin } - return svc.createToken(ctx, usr.ID) + return svc.createSessionToken(ctx, usr.ID) } func (svc *UserService) Logout(ctx context.Context, token string) error { @@ -174,9 +197,7 @@ func (svc *UserService) RenewToken(ctx context.Context, token string) (UserAuthT return UserAuthTokenDetail{}, ErrorInvalidToken } - newToken, _ := svc.createToken(ctx, dbToken.ID) - - return newToken, nil + return svc.createSessionToken(ctx, dbToken.ID) } // DeleteSelf deletes the user that is currently logged based of the provided UUID diff --git a/backend/internal/data/ent/authroles.go b/backend/internal/data/ent/authroles.go new file mode 100644 index 0000000..cfd2be4 --- /dev/null +++ b/backend/internal/data/ent/authroles.go @@ -0,0 +1,141 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + + "entgo.io/ent/dialect/sql" + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" + "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" +) + +// AuthRoles is the model entity for the AuthRoles schema. +type AuthRoles struct { + config `json:"-"` + // ID of the ent. + ID int `json:"id,omitempty"` + // Role holds the value of the "role" field. + Role authroles.Role `json:"role,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the AuthRolesQuery when eager-loading is set. + Edges AuthRolesEdges `json:"edges"` + auth_tokens_roles *uuid.UUID +} + +// AuthRolesEdges holds the relations/edges for other nodes in the graph. +type AuthRolesEdges struct { + // Token holds the value of the token edge. + Token *AuthTokens `json:"token,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// TokenOrErr returns the Token value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e AuthRolesEdges) TokenOrErr() (*AuthTokens, error) { + if e.loadedTypes[0] { + if e.Token == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: authtokens.Label} + } + return e.Token, nil + } + return nil, &NotLoadedError{edge: "token"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*AuthRoles) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case authroles.FieldID: + values[i] = new(sql.NullInt64) + case authroles.FieldRole: + values[i] = new(sql.NullString) + case authroles.ForeignKeys[0]: // auth_tokens_roles + values[i] = &sql.NullScanner{S: new(uuid.UUID)} + default: + return nil, fmt.Errorf("unexpected column %q for type AuthRoles", columns[i]) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the AuthRoles fields. +func (ar *AuthRoles) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case authroles.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + ar.ID = int(value.Int64) + case authroles.FieldRole: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field role", values[i]) + } else if value.Valid { + ar.Role = authroles.Role(value.String) + } + case authroles.ForeignKeys[0]: + if value, ok := values[i].(*sql.NullScanner); !ok { + return fmt.Errorf("unexpected type %T for field auth_tokens_roles", values[i]) + } else if value.Valid { + ar.auth_tokens_roles = new(uuid.UUID) + *ar.auth_tokens_roles = *value.S.(*uuid.UUID) + } + } + } + return nil +} + +// QueryToken queries the "token" edge of the AuthRoles entity. +func (ar *AuthRoles) QueryToken() *AuthTokensQuery { + return (&AuthRolesClient{config: ar.config}).QueryToken(ar) +} + +// Update returns a builder for updating this AuthRoles. +// Note that you need to call AuthRoles.Unwrap() before calling this method if this AuthRoles +// was returned from a transaction, and the transaction was committed or rolled back. +func (ar *AuthRoles) Update() *AuthRolesUpdateOne { + return (&AuthRolesClient{config: ar.config}).UpdateOne(ar) +} + +// Unwrap unwraps the AuthRoles entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (ar *AuthRoles) Unwrap() *AuthRoles { + _tx, ok := ar.config.driver.(*txDriver) + if !ok { + panic("ent: AuthRoles is not a transactional entity") + } + ar.config.driver = _tx.drv + return ar +} + +// String implements the fmt.Stringer. +func (ar *AuthRoles) String() string { + var builder strings.Builder + builder.WriteString("AuthRoles(") + builder.WriteString(fmt.Sprintf("id=%v, ", ar.ID)) + builder.WriteString("role=") + builder.WriteString(fmt.Sprintf("%v", ar.Role)) + builder.WriteByte(')') + return builder.String() +} + +// AuthRolesSlice is a parsable slice of AuthRoles. +type AuthRolesSlice []*AuthRoles + +func (ar AuthRolesSlice) config(cfg config) { + for _i := range ar { + ar[_i].config = cfg + } +} diff --git a/backend/internal/data/ent/authroles/authroles.go b/backend/internal/data/ent/authroles/authroles.go new file mode 100644 index 0000000..b414e60 --- /dev/null +++ b/backend/internal/data/ent/authroles/authroles.go @@ -0,0 +1,81 @@ +// Code generated by ent, DO NOT EDIT. + +package authroles + +import ( + "fmt" +) + +const ( + // Label holds the string label denoting the authroles type in the database. + Label = "auth_roles" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldRole holds the string denoting the role field in the database. + FieldRole = "role" + // EdgeToken holds the string denoting the token edge name in mutations. + EdgeToken = "token" + // Table holds the table name of the authroles in the database. + Table = "auth_roles" + // TokenTable is the table that holds the token relation/edge. + TokenTable = "auth_roles" + // TokenInverseTable is the table name for the AuthTokens entity. + // It exists in this package in order to avoid circular dependency with the "authtokens" package. + TokenInverseTable = "auth_tokens" + // TokenColumn is the table column denoting the token relation/edge. + TokenColumn = "auth_tokens_roles" +) + +// Columns holds all SQL columns for authroles fields. +var Columns = []string{ + FieldID, + FieldRole, +} + +// ForeignKeys holds the SQL foreign-keys that are owned by the "auth_roles" +// table and are not defined as standalone fields in the schema. +var ForeignKeys = []string{ + "auth_tokens_roles", +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + for i := range ForeignKeys { + if column == ForeignKeys[i] { + return true + } + } + return false +} + +// Role defines the type for the "role" enum field. +type Role string + +// RoleUser is the default value of the Role enum. +const DefaultRole = RoleUser + +// Role values. +const ( + RoleAdmin Role = "admin" + RoleUser Role = "user" + RoleAttachments Role = "attachments" +) + +func (r Role) String() string { + return string(r) +} + +// RoleValidator is a validator for the "role" field enum values. It is called by the builders before save. +func RoleValidator(r Role) error { + switch r { + case RoleAdmin, RoleUser, RoleAttachments: + return nil + default: + return fmt.Errorf("authroles: invalid enum value for role field: %q", r) + } +} diff --git a/backend/internal/data/ent/authroles/where.go b/backend/internal/data/ent/authroles/where.go new file mode 100644 index 0000000..b3344ec --- /dev/null +++ b/backend/internal/data/ent/authroles/where.go @@ -0,0 +1,176 @@ +// Code generated by ent, DO NOT EDIT. + +package authroles + +import ( + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldID), id)) + }) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldID), id)) + }) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldID), id)) + }) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + v := make([]any, len(ids)) + for i := range v { + v[i] = ids[i] + } + s.Where(sql.In(s.C(FieldID), v...)) + }) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + v := make([]any, len(ids)) + for i := range v { + v[i] = ids[i] + } + s.Where(sql.NotIn(s.C(FieldID), v...)) + }) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldID), id)) + }) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldID), id)) + }) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldID), id)) + }) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldID), id)) + }) +} + +// RoleEQ applies the EQ predicate on the "role" field. +func RoleEQ(v Role) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldRole), v)) + }) +} + +// RoleNEQ applies the NEQ predicate on the "role" field. +func RoleNEQ(v Role) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldRole), v)) + }) +} + +// RoleIn applies the In predicate on the "role" field. +func RoleIn(vs ...Role) predicate.AuthRoles { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.In(s.C(FieldRole), v...)) + }) +} + +// RoleNotIn applies the NotIn predicate on the "role" field. +func RoleNotIn(vs ...Role) predicate.AuthRoles { + v := make([]any, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.NotIn(s.C(FieldRole), v...)) + }) +} + +// HasToken applies the HasEdge predicate on the "token" edge. +func HasToken() predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(TokenTable, FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, TokenTable, TokenColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasTokenWith applies the HasEdge predicate on the "token" edge with a given conditions (other predicates). +func HasTokenWith(preds ...predicate.AuthTokens) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(TokenInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, TokenTable, TokenColumn), + ) + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.AuthRoles) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for _, p := range predicates { + p(s1) + } + s.Where(s1.P()) + }) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.AuthRoles) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + s1 := s.Clone().SetP(nil) + for i, p := range predicates { + if i > 0 { + s1.Or() + } + p(s1) + } + s.Where(s1.P()) + }) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.AuthRoles) predicate.AuthRoles { + return predicate.AuthRoles(func(s *sql.Selector) { + p(s.Not()) + }) +} diff --git a/backend/internal/data/ent/authroles_create.go b/backend/internal/data/ent/authroles_create.go new file mode 100644 index 0000000..3f66cd0 --- /dev/null +++ b/backend/internal/data/ent/authroles_create.go @@ -0,0 +1,286 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" + "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" +) + +// AuthRolesCreate is the builder for creating a AuthRoles entity. +type AuthRolesCreate struct { + config + mutation *AuthRolesMutation + hooks []Hook +} + +// SetRole sets the "role" field. +func (arc *AuthRolesCreate) SetRole(a authroles.Role) *AuthRolesCreate { + arc.mutation.SetRole(a) + return arc +} + +// SetNillableRole sets the "role" field if the given value is not nil. +func (arc *AuthRolesCreate) SetNillableRole(a *authroles.Role) *AuthRolesCreate { + if a != nil { + arc.SetRole(*a) + } + return arc +} + +// SetTokenID sets the "token" edge to the AuthTokens entity by ID. +func (arc *AuthRolesCreate) SetTokenID(id uuid.UUID) *AuthRolesCreate { + arc.mutation.SetTokenID(id) + return arc +} + +// SetNillableTokenID sets the "token" edge to the AuthTokens entity by ID if the given value is not nil. +func (arc *AuthRolesCreate) SetNillableTokenID(id *uuid.UUID) *AuthRolesCreate { + if id != nil { + arc = arc.SetTokenID(*id) + } + return arc +} + +// SetToken sets the "token" edge to the AuthTokens entity. +func (arc *AuthRolesCreate) SetToken(a *AuthTokens) *AuthRolesCreate { + return arc.SetTokenID(a.ID) +} + +// Mutation returns the AuthRolesMutation object of the builder. +func (arc *AuthRolesCreate) Mutation() *AuthRolesMutation { + return arc.mutation +} + +// Save creates the AuthRoles in the database. +func (arc *AuthRolesCreate) Save(ctx context.Context) (*AuthRoles, error) { + var ( + err error + node *AuthRoles + ) + arc.defaults() + if len(arc.hooks) == 0 { + if err = arc.check(); err != nil { + return nil, err + } + node, err = arc.sqlSave(ctx) + } else { + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AuthRolesMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err = arc.check(); err != nil { + return nil, err + } + arc.mutation = mutation + if node, err = arc.sqlSave(ctx); err != nil { + return nil, err + } + mutation.id = &node.ID + mutation.done = true + return node, err + }) + for i := len(arc.hooks) - 1; i >= 0; i-- { + if arc.hooks[i] == nil { + return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = arc.hooks[i](mut) + } + v, err := mut.Mutate(ctx, arc.mutation) + if err != nil { + return nil, err + } + nv, ok := v.(*AuthRoles) + if !ok { + return nil, fmt.Errorf("unexpected node type %T returned from AuthRolesMutation", v) + } + node = nv + } + return node, err +} + +// SaveX calls Save and panics if Save returns an error. +func (arc *AuthRolesCreate) SaveX(ctx context.Context) *AuthRoles { + v, err := arc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (arc *AuthRolesCreate) Exec(ctx context.Context) error { + _, err := arc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (arc *AuthRolesCreate) ExecX(ctx context.Context) { + if err := arc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (arc *AuthRolesCreate) defaults() { + if _, ok := arc.mutation.Role(); !ok { + v := authroles.DefaultRole + arc.mutation.SetRole(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (arc *AuthRolesCreate) check() error { + if _, ok := arc.mutation.Role(); !ok { + return &ValidationError{Name: "role", err: errors.New(`ent: missing required field "AuthRoles.role"`)} + } + if v, ok := arc.mutation.Role(); ok { + if err := authroles.RoleValidator(v); err != nil { + return &ValidationError{Name: "role", err: fmt.Errorf(`ent: validator failed for field "AuthRoles.role": %w`, err)} + } + } + return nil +} + +func (arc *AuthRolesCreate) sqlSave(ctx context.Context) (*AuthRoles, error) { + _node, _spec := arc.createSpec() + if err := sqlgraph.CreateNode(ctx, arc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + id := _spec.ID.Value.(int64) + _node.ID = int(id) + return _node, nil +} + +func (arc *AuthRolesCreate) createSpec() (*AuthRoles, *sqlgraph.CreateSpec) { + var ( + _node = &AuthRoles{config: arc.config} + _spec = &sqlgraph.CreateSpec{ + Table: authroles.Table, + ID: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: authroles.FieldID, + }, + } + ) + if value, ok := arc.mutation.Role(); ok { + _spec.SetField(authroles.FieldRole, field.TypeEnum, value) + _node.Role = value + } + if nodes := arc.mutation.TokenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: authroles.TokenTable, + Columns: []string{authroles.TokenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: authtokens.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.auth_tokens_roles = &nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// AuthRolesCreateBulk is the builder for creating many AuthRoles entities in bulk. +type AuthRolesCreateBulk struct { + config + builders []*AuthRolesCreate +} + +// Save creates the AuthRoles entities in the database. +func (arcb *AuthRolesCreateBulk) Save(ctx context.Context) ([]*AuthRoles, error) { + specs := make([]*sqlgraph.CreateSpec, len(arcb.builders)) + nodes := make([]*AuthRoles, len(arcb.builders)) + mutators := make([]Mutator, len(arcb.builders)) + for i := range arcb.builders { + func(i int, root context.Context) { + builder := arcb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AuthRolesMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + nodes[i], specs[i] = builder.createSpec() + var err error + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, arcb.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, arcb.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + if specs[i].ID.Value != nil { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int(id) + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, arcb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (arcb *AuthRolesCreateBulk) SaveX(ctx context.Context) []*AuthRoles { + v, err := arcb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (arcb *AuthRolesCreateBulk) Exec(ctx context.Context) error { + _, err := arcb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (arcb *AuthRolesCreateBulk) ExecX(ctx context.Context) { + if err := arcb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/internal/data/ent/authroles_delete.go b/backend/internal/data/ent/authroles_delete.go new file mode 100644 index 0000000..10b7c00 --- /dev/null +++ b/backend/internal/data/ent/authroles_delete.go @@ -0,0 +1,115 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" + "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" +) + +// AuthRolesDelete is the builder for deleting a AuthRoles entity. +type AuthRolesDelete struct { + config + hooks []Hook + mutation *AuthRolesMutation +} + +// Where appends a list predicates to the AuthRolesDelete builder. +func (ard *AuthRolesDelete) Where(ps ...predicate.AuthRoles) *AuthRolesDelete { + ard.mutation.Where(ps...) + return ard +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (ard *AuthRolesDelete) Exec(ctx context.Context) (int, error) { + var ( + err error + affected int + ) + if len(ard.hooks) == 0 { + affected, err = ard.sqlExec(ctx) + } else { + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AuthRolesMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + ard.mutation = mutation + affected, err = ard.sqlExec(ctx) + mutation.done = true + return affected, err + }) + for i := len(ard.hooks) - 1; i >= 0; i-- { + if ard.hooks[i] == nil { + return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = ard.hooks[i](mut) + } + if _, err := mut.Mutate(ctx, ard.mutation); err != nil { + return 0, err + } + } + return affected, err +} + +// ExecX is like Exec, but panics if an error occurs. +func (ard *AuthRolesDelete) ExecX(ctx context.Context) int { + n, err := ard.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (ard *AuthRolesDelete) sqlExec(ctx context.Context) (int, error) { + _spec := &sqlgraph.DeleteSpec{ + Node: &sqlgraph.NodeSpec{ + Table: authroles.Table, + ID: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: authroles.FieldID, + }, + }, + } + if ps := ard.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, ard.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return affected, err +} + +// AuthRolesDeleteOne is the builder for deleting a single AuthRoles entity. +type AuthRolesDeleteOne struct { + ard *AuthRolesDelete +} + +// Exec executes the deletion query. +func (ardo *AuthRolesDeleteOne) Exec(ctx context.Context) error { + n, err := ardo.ard.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{authroles.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (ardo *AuthRolesDeleteOne) ExecX(ctx context.Context) { + ardo.ard.ExecX(ctx) +} diff --git a/backend/internal/data/ent/authroles_query.go b/backend/internal/data/ent/authroles_query.go new file mode 100644 index 0000000..cc3e56c --- /dev/null +++ b/backend/internal/data/ent/authroles_query.go @@ -0,0 +1,633 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" + "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" + "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" +) + +// AuthRolesQuery is the builder for querying AuthRoles entities. +type AuthRolesQuery struct { + config + limit *int + offset *int + unique *bool + order []OrderFunc + fields []string + predicates []predicate.AuthRoles + withToken *AuthTokensQuery + withFKs bool + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the AuthRolesQuery builder. +func (arq *AuthRolesQuery) Where(ps ...predicate.AuthRoles) *AuthRolesQuery { + arq.predicates = append(arq.predicates, ps...) + return arq +} + +// Limit adds a limit step to the query. +func (arq *AuthRolesQuery) Limit(limit int) *AuthRolesQuery { + arq.limit = &limit + return arq +} + +// Offset adds an offset step to the query. +func (arq *AuthRolesQuery) Offset(offset int) *AuthRolesQuery { + arq.offset = &offset + return arq +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (arq *AuthRolesQuery) Unique(unique bool) *AuthRolesQuery { + arq.unique = &unique + return arq +} + +// Order adds an order step to the query. +func (arq *AuthRolesQuery) Order(o ...OrderFunc) *AuthRolesQuery { + arq.order = append(arq.order, o...) + return arq +} + +// QueryToken chains the current query on the "token" edge. +func (arq *AuthRolesQuery) QueryToken() *AuthTokensQuery { + query := &AuthTokensQuery{config: arq.config} + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := arq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := arq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(authroles.Table, authroles.FieldID, selector), + sqlgraph.To(authtokens.Table, authtokens.FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, authroles.TokenTable, authroles.TokenColumn), + ) + fromU = sqlgraph.SetNeighbors(arq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first AuthRoles entity from the query. +// Returns a *NotFoundError when no AuthRoles was found. +func (arq *AuthRolesQuery) First(ctx context.Context) (*AuthRoles, error) { + nodes, err := arq.Limit(1).All(ctx) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{authroles.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (arq *AuthRolesQuery) FirstX(ctx context.Context) *AuthRoles { + node, err := arq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first AuthRoles ID from the query. +// Returns a *NotFoundError when no AuthRoles ID was found. +func (arq *AuthRolesQuery) FirstID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = arq.Limit(1).IDs(ctx); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{authroles.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (arq *AuthRolesQuery) FirstIDX(ctx context.Context) int { + id, err := arq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single AuthRoles entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one AuthRoles entity is found. +// Returns a *NotFoundError when no AuthRoles entities are found. +func (arq *AuthRolesQuery) Only(ctx context.Context) (*AuthRoles, error) { + nodes, err := arq.Limit(2).All(ctx) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{authroles.Label} + default: + return nil, &NotSingularError{authroles.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (arq *AuthRolesQuery) OnlyX(ctx context.Context) *AuthRoles { + node, err := arq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only AuthRoles ID in the query. +// Returns a *NotSingularError when more than one AuthRoles ID is found. +// Returns a *NotFoundError when no entities are found. +func (arq *AuthRolesQuery) OnlyID(ctx context.Context) (id int, err error) { + var ids []int + if ids, err = arq.Limit(2).IDs(ctx); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{authroles.Label} + default: + err = &NotSingularError{authroles.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (arq *AuthRolesQuery) OnlyIDX(ctx context.Context) int { + id, err := arq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of AuthRolesSlice. +func (arq *AuthRolesQuery) All(ctx context.Context) ([]*AuthRoles, error) { + if err := arq.prepareQuery(ctx); err != nil { + return nil, err + } + return arq.sqlAll(ctx) +} + +// AllX is like All, but panics if an error occurs. +func (arq *AuthRolesQuery) AllX(ctx context.Context) []*AuthRoles { + nodes, err := arq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of AuthRoles IDs. +func (arq *AuthRolesQuery) IDs(ctx context.Context) ([]int, error) { + var ids []int + if err := arq.Select(authroles.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (arq *AuthRolesQuery) IDsX(ctx context.Context) []int { + ids, err := arq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (arq *AuthRolesQuery) Count(ctx context.Context) (int, error) { + if err := arq.prepareQuery(ctx); err != nil { + return 0, err + } + return arq.sqlCount(ctx) +} + +// CountX is like Count, but panics if an error occurs. +func (arq *AuthRolesQuery) CountX(ctx context.Context) int { + count, err := arq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (arq *AuthRolesQuery) Exist(ctx context.Context) (bool, error) { + if err := arq.prepareQuery(ctx); err != nil { + return false, err + } + return arq.sqlExist(ctx) +} + +// ExistX is like Exist, but panics if an error occurs. +func (arq *AuthRolesQuery) ExistX(ctx context.Context) bool { + exist, err := arq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the AuthRolesQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (arq *AuthRolesQuery) Clone() *AuthRolesQuery { + if arq == nil { + return nil + } + return &AuthRolesQuery{ + config: arq.config, + limit: arq.limit, + offset: arq.offset, + order: append([]OrderFunc{}, arq.order...), + predicates: append([]predicate.AuthRoles{}, arq.predicates...), + withToken: arq.withToken.Clone(), + // clone intermediate query. + sql: arq.sql.Clone(), + path: arq.path, + unique: arq.unique, + } +} + +// WithToken tells the query-builder to eager-load the nodes that are connected to +// the "token" edge. The optional arguments are used to configure the query builder of the edge. +func (arq *AuthRolesQuery) WithToken(opts ...func(*AuthTokensQuery)) *AuthRolesQuery { + query := &AuthTokensQuery{config: arq.config} + for _, opt := range opts { + opt(query) + } + arq.withToken = query + return arq +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Role authroles.Role `json:"role,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.AuthRoles.Query(). +// GroupBy(authroles.FieldRole). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (arq *AuthRolesQuery) GroupBy(field string, fields ...string) *AuthRolesGroupBy { + grbuild := &AuthRolesGroupBy{config: arq.config} + grbuild.fields = append([]string{field}, fields...) + grbuild.path = func(ctx context.Context) (prev *sql.Selector, err error) { + if err := arq.prepareQuery(ctx); err != nil { + return nil, err + } + return arq.sqlQuery(ctx), nil + } + grbuild.label = authroles.Label + grbuild.flds, grbuild.scan = &grbuild.fields, grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Role authroles.Role `json:"role,omitempty"` +// } +// +// client.AuthRoles.Query(). +// Select(authroles.FieldRole). +// Scan(ctx, &v) +func (arq *AuthRolesQuery) Select(fields ...string) *AuthRolesSelect { + arq.fields = append(arq.fields, fields...) + selbuild := &AuthRolesSelect{AuthRolesQuery: arq} + selbuild.label = authroles.Label + selbuild.flds, selbuild.scan = &arq.fields, selbuild.Scan + return selbuild +} + +// Aggregate returns a AuthRolesSelect configured with the given aggregations. +func (arq *AuthRolesQuery) Aggregate(fns ...AggregateFunc) *AuthRolesSelect { + return arq.Select().Aggregate(fns...) +} + +func (arq *AuthRolesQuery) prepareQuery(ctx context.Context) error { + for _, f := range arq.fields { + if !authroles.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if arq.path != nil { + prev, err := arq.path(ctx) + if err != nil { + return err + } + arq.sql = prev + } + return nil +} + +func (arq *AuthRolesQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuthRoles, error) { + var ( + nodes = []*AuthRoles{} + withFKs = arq.withFKs + _spec = arq.querySpec() + loadedTypes = [1]bool{ + arq.withToken != nil, + } + ) + if arq.withToken != nil { + withFKs = true + } + if withFKs { + _spec.Node.Columns = append(_spec.Node.Columns, authroles.ForeignKeys...) + } + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*AuthRoles).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &AuthRoles{config: arq.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, arq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := arq.withToken; query != nil { + if err := arq.loadToken(ctx, query, nodes, nil, + func(n *AuthRoles, e *AuthTokens) { n.Edges.Token = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (arq *AuthRolesQuery) loadToken(ctx context.Context, query *AuthTokensQuery, nodes []*AuthRoles, init func(*AuthRoles), assign func(*AuthRoles, *AuthTokens)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*AuthRoles) + for i := range nodes { + if nodes[i].auth_tokens_roles == nil { + continue + } + fk := *nodes[i].auth_tokens_roles + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + query.Where(authtokens.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "auth_tokens_roles" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (arq *AuthRolesQuery) sqlCount(ctx context.Context) (int, error) { + _spec := arq.querySpec() + _spec.Node.Columns = arq.fields + if len(arq.fields) > 0 { + _spec.Unique = arq.unique != nil && *arq.unique + } + return sqlgraph.CountNodes(ctx, arq.driver, _spec) +} + +func (arq *AuthRolesQuery) sqlExist(ctx context.Context) (bool, error) { + switch _, err := arq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +func (arq *AuthRolesQuery) querySpec() *sqlgraph.QuerySpec { + _spec := &sqlgraph.QuerySpec{ + Node: &sqlgraph.NodeSpec{ + Table: authroles.Table, + Columns: authroles.Columns, + ID: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: authroles.FieldID, + }, + }, + From: arq.sql, + Unique: true, + } + if unique := arq.unique; unique != nil { + _spec.Unique = *unique + } + if fields := arq.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, authroles.FieldID) + for i := range fields { + if fields[i] != authroles.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + } + if ps := arq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := arq.limit; limit != nil { + _spec.Limit = *limit + } + if offset := arq.offset; offset != nil { + _spec.Offset = *offset + } + if ps := arq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (arq *AuthRolesQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(arq.driver.Dialect()) + t1 := builder.Table(authroles.Table) + columns := arq.fields + if len(columns) == 0 { + columns = authroles.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if arq.sql != nil { + selector = arq.sql + selector.Select(selector.Columns(columns...)...) + } + if arq.unique != nil && *arq.unique { + selector.Distinct() + } + for _, p := range arq.predicates { + p(selector) + } + for _, p := range arq.order { + p(selector) + } + if offset := arq.offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := arq.limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// AuthRolesGroupBy is the group-by builder for AuthRoles entities. +type AuthRolesGroupBy struct { + config + selector + fields []string + fns []AggregateFunc + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (argb *AuthRolesGroupBy) Aggregate(fns ...AggregateFunc) *AuthRolesGroupBy { + argb.fns = append(argb.fns, fns...) + return argb +} + +// Scan applies the group-by query and scans the result into the given value. +func (argb *AuthRolesGroupBy) Scan(ctx context.Context, v any) error { + query, err := argb.path(ctx) + if err != nil { + return err + } + argb.sql = query + return argb.sqlScan(ctx, v) +} + +func (argb *AuthRolesGroupBy) sqlScan(ctx context.Context, v any) error { + for _, f := range argb.fields { + if !authroles.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for group-by", f)} + } + } + selector := argb.sqlQuery() + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := argb.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +func (argb *AuthRolesGroupBy) sqlQuery() *sql.Selector { + selector := argb.sql.Select() + aggregation := make([]string, 0, len(argb.fns)) + for _, fn := range argb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(argb.fields)+len(argb.fns)) + for _, f := range argb.fields { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + return selector.GroupBy(selector.Columns(argb.fields...)...) +} + +// AuthRolesSelect is the builder for selecting fields of AuthRoles entities. +type AuthRolesSelect struct { + *AuthRolesQuery + selector + // intermediate query (i.e. traversal path). + sql *sql.Selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (ars *AuthRolesSelect) Aggregate(fns ...AggregateFunc) *AuthRolesSelect { + ars.fns = append(ars.fns, fns...) + return ars +} + +// Scan applies the selector query and scans the result into the given value. +func (ars *AuthRolesSelect) Scan(ctx context.Context, v any) error { + if err := ars.prepareQuery(ctx); err != nil { + return err + } + ars.sql = ars.AuthRolesQuery.sqlQuery(ctx) + return ars.sqlScan(ctx, v) +} + +func (ars *AuthRolesSelect) sqlScan(ctx context.Context, v any) error { + aggregation := make([]string, 0, len(ars.fns)) + for _, fn := range ars.fns { + aggregation = append(aggregation, fn(ars.sql)) + } + switch n := len(*ars.selector.flds); { + case n == 0 && len(aggregation) > 0: + ars.sql.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + ars.sql.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := ars.sql.Query() + if err := ars.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/internal/data/ent/authroles_update.go b/backend/internal/data/ent/authroles_update.go new file mode 100644 index 0000000..6aa9b87 --- /dev/null +++ b/backend/internal/data/ent/authroles_update.go @@ -0,0 +1,433 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" + "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" + "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" +) + +// AuthRolesUpdate is the builder for updating AuthRoles entities. +type AuthRolesUpdate struct { + config + hooks []Hook + mutation *AuthRolesMutation +} + +// Where appends a list predicates to the AuthRolesUpdate builder. +func (aru *AuthRolesUpdate) Where(ps ...predicate.AuthRoles) *AuthRolesUpdate { + aru.mutation.Where(ps...) + return aru +} + +// SetRole sets the "role" field. +func (aru *AuthRolesUpdate) SetRole(a authroles.Role) *AuthRolesUpdate { + aru.mutation.SetRole(a) + return aru +} + +// SetNillableRole sets the "role" field if the given value is not nil. +func (aru *AuthRolesUpdate) SetNillableRole(a *authroles.Role) *AuthRolesUpdate { + if a != nil { + aru.SetRole(*a) + } + return aru +} + +// SetTokenID sets the "token" edge to the AuthTokens entity by ID. +func (aru *AuthRolesUpdate) SetTokenID(id uuid.UUID) *AuthRolesUpdate { + aru.mutation.SetTokenID(id) + return aru +} + +// SetNillableTokenID sets the "token" edge to the AuthTokens entity by ID if the given value is not nil. +func (aru *AuthRolesUpdate) SetNillableTokenID(id *uuid.UUID) *AuthRolesUpdate { + if id != nil { + aru = aru.SetTokenID(*id) + } + return aru +} + +// SetToken sets the "token" edge to the AuthTokens entity. +func (aru *AuthRolesUpdate) SetToken(a *AuthTokens) *AuthRolesUpdate { + return aru.SetTokenID(a.ID) +} + +// Mutation returns the AuthRolesMutation object of the builder. +func (aru *AuthRolesUpdate) Mutation() *AuthRolesMutation { + return aru.mutation +} + +// ClearToken clears the "token" edge to the AuthTokens entity. +func (aru *AuthRolesUpdate) ClearToken() *AuthRolesUpdate { + aru.mutation.ClearToken() + return aru +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (aru *AuthRolesUpdate) Save(ctx context.Context) (int, error) { + var ( + err error + affected int + ) + if len(aru.hooks) == 0 { + if err = aru.check(); err != nil { + return 0, err + } + affected, err = aru.sqlSave(ctx) + } else { + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AuthRolesMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err = aru.check(); err != nil { + return 0, err + } + aru.mutation = mutation + affected, err = aru.sqlSave(ctx) + mutation.done = true + return affected, err + }) + for i := len(aru.hooks) - 1; i >= 0; i-- { + if aru.hooks[i] == nil { + return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = aru.hooks[i](mut) + } + if _, err := mut.Mutate(ctx, aru.mutation); err != nil { + return 0, err + } + } + return affected, err +} + +// SaveX is like Save, but panics if an error occurs. +func (aru *AuthRolesUpdate) SaveX(ctx context.Context) int { + affected, err := aru.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (aru *AuthRolesUpdate) Exec(ctx context.Context) error { + _, err := aru.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (aru *AuthRolesUpdate) ExecX(ctx context.Context) { + if err := aru.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (aru *AuthRolesUpdate) check() error { + if v, ok := aru.mutation.Role(); ok { + if err := authroles.RoleValidator(v); err != nil { + return &ValidationError{Name: "role", err: fmt.Errorf(`ent: validator failed for field "AuthRoles.role": %w`, err)} + } + } + return nil +} + +func (aru *AuthRolesUpdate) sqlSave(ctx context.Context) (n int, err error) { + _spec := &sqlgraph.UpdateSpec{ + Node: &sqlgraph.NodeSpec{ + Table: authroles.Table, + Columns: authroles.Columns, + ID: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: authroles.FieldID, + }, + }, + } + if ps := aru.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := aru.mutation.Role(); ok { + _spec.SetField(authroles.FieldRole, field.TypeEnum, value) + } + if aru.mutation.TokenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: authroles.TokenTable, + Columns: []string{authroles.TokenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: authtokens.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := aru.mutation.TokenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: authroles.TokenTable, + Columns: []string{authroles.TokenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: authtokens.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if n, err = sqlgraph.UpdateNodes(ctx, aru.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{authroles.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + return n, nil +} + +// AuthRolesUpdateOne is the builder for updating a single AuthRoles entity. +type AuthRolesUpdateOne struct { + config + fields []string + hooks []Hook + mutation *AuthRolesMutation +} + +// SetRole sets the "role" field. +func (aruo *AuthRolesUpdateOne) SetRole(a authroles.Role) *AuthRolesUpdateOne { + aruo.mutation.SetRole(a) + return aruo +} + +// SetNillableRole sets the "role" field if the given value is not nil. +func (aruo *AuthRolesUpdateOne) SetNillableRole(a *authroles.Role) *AuthRolesUpdateOne { + if a != nil { + aruo.SetRole(*a) + } + return aruo +} + +// SetTokenID sets the "token" edge to the AuthTokens entity by ID. +func (aruo *AuthRolesUpdateOne) SetTokenID(id uuid.UUID) *AuthRolesUpdateOne { + aruo.mutation.SetTokenID(id) + return aruo +} + +// SetNillableTokenID sets the "token" edge to the AuthTokens entity by ID if the given value is not nil. +func (aruo *AuthRolesUpdateOne) SetNillableTokenID(id *uuid.UUID) *AuthRolesUpdateOne { + if id != nil { + aruo = aruo.SetTokenID(*id) + } + return aruo +} + +// SetToken sets the "token" edge to the AuthTokens entity. +func (aruo *AuthRolesUpdateOne) SetToken(a *AuthTokens) *AuthRolesUpdateOne { + return aruo.SetTokenID(a.ID) +} + +// Mutation returns the AuthRolesMutation object of the builder. +func (aruo *AuthRolesUpdateOne) Mutation() *AuthRolesMutation { + return aruo.mutation +} + +// ClearToken clears the "token" edge to the AuthTokens entity. +func (aruo *AuthRolesUpdateOne) ClearToken() *AuthRolesUpdateOne { + aruo.mutation.ClearToken() + return aruo +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (aruo *AuthRolesUpdateOne) Select(field string, fields ...string) *AuthRolesUpdateOne { + aruo.fields = append([]string{field}, fields...) + return aruo +} + +// Save executes the query and returns the updated AuthRoles entity. +func (aruo *AuthRolesUpdateOne) Save(ctx context.Context) (*AuthRoles, error) { + var ( + err error + node *AuthRoles + ) + if len(aruo.hooks) == 0 { + if err = aruo.check(); err != nil { + return nil, err + } + node, err = aruo.sqlSave(ctx) + } else { + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*AuthRolesMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err = aruo.check(); err != nil { + return nil, err + } + aruo.mutation = mutation + node, err = aruo.sqlSave(ctx) + mutation.done = true + return node, err + }) + for i := len(aruo.hooks) - 1; i >= 0; i-- { + if aruo.hooks[i] == nil { + return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") + } + mut = aruo.hooks[i](mut) + } + v, err := mut.Mutate(ctx, aruo.mutation) + if err != nil { + return nil, err + } + nv, ok := v.(*AuthRoles) + if !ok { + return nil, fmt.Errorf("unexpected node type %T returned from AuthRolesMutation", v) + } + node = nv + } + return node, err +} + +// SaveX is like Save, but panics if an error occurs. +func (aruo *AuthRolesUpdateOne) SaveX(ctx context.Context) *AuthRoles { + node, err := aruo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (aruo *AuthRolesUpdateOne) Exec(ctx context.Context) error { + _, err := aruo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (aruo *AuthRolesUpdateOne) ExecX(ctx context.Context) { + if err := aruo.Exec(ctx); err != nil { + panic(err) + } +} + +// check runs all checks and user-defined validators on the builder. +func (aruo *AuthRolesUpdateOne) check() error { + if v, ok := aruo.mutation.Role(); ok { + if err := authroles.RoleValidator(v); err != nil { + return &ValidationError{Name: "role", err: fmt.Errorf(`ent: validator failed for field "AuthRoles.role": %w`, err)} + } + } + return nil +} + +func (aruo *AuthRolesUpdateOne) sqlSave(ctx context.Context) (_node *AuthRoles, err error) { + _spec := &sqlgraph.UpdateSpec{ + Node: &sqlgraph.NodeSpec{ + Table: authroles.Table, + Columns: authroles.Columns, + ID: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: authroles.FieldID, + }, + }, + } + id, ok := aruo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "AuthRoles.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := aruo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, authroles.FieldID) + for _, f := range fields { + if !authroles.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != authroles.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := aruo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := aruo.mutation.Role(); ok { + _spec.SetField(authroles.FieldRole, field.TypeEnum, value) + } + if aruo.mutation.TokenCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: authroles.TokenTable, + Columns: []string{authroles.TokenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: authtokens.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := aruo.mutation.TokenIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: true, + Table: authroles.TokenTable, + Columns: []string{authroles.TokenColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeUUID, + Column: authtokens.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &AuthRoles{config: aruo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, aruo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{authroles.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + return _node, nil +} diff --git a/backend/internal/data/ent/authtokens.go b/backend/internal/data/ent/authtokens.go index 6e8e53d..8945152 100644 --- a/backend/internal/data/ent/authtokens.go +++ b/backend/internal/data/ent/authtokens.go @@ -9,6 +9,7 @@ import ( "entgo.io/ent/dialect/sql" "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/user" ) @@ -36,9 +37,11 @@ type AuthTokens struct { type AuthTokensEdges struct { // User holds the value of the user edge. User *User `json:"user,omitempty"` + // Roles holds the value of the roles edge. + Roles *AuthRoles `json:"roles,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [1]bool + loadedTypes [2]bool } // UserOrErr returns the User value or an error if the edge @@ -54,6 +57,19 @@ func (e AuthTokensEdges) UserOrErr() (*User, error) { return nil, &NotLoadedError{edge: "user"} } +// RolesOrErr returns the Roles value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e AuthTokensEdges) RolesOrErr() (*AuthRoles, error) { + if e.loadedTypes[1] { + if e.Roles == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: authroles.Label} + } + return e.Roles, nil + } + return nil, &NotLoadedError{edge: "roles"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*AuthTokens) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -129,6 +145,11 @@ func (at *AuthTokens) QueryUser() *UserQuery { return (&AuthTokensClient{config: at.config}).QueryUser(at) } +// QueryRoles queries the "roles" edge of the AuthTokens entity. +func (at *AuthTokens) QueryRoles() *AuthRolesQuery { + return (&AuthTokensClient{config: at.config}).QueryRoles(at) +} + // Update returns a builder for updating this AuthTokens. // Note that you need to call AuthTokens.Unwrap() before calling this method if this AuthTokens // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/backend/internal/data/ent/authtokens/authtokens.go b/backend/internal/data/ent/authtokens/authtokens.go index af22805..2d809f4 100644 --- a/backend/internal/data/ent/authtokens/authtokens.go +++ b/backend/internal/data/ent/authtokens/authtokens.go @@ -23,6 +23,8 @@ const ( FieldExpiresAt = "expires_at" // EdgeUser holds the string denoting the user edge name in mutations. EdgeUser = "user" + // EdgeRoles holds the string denoting the roles edge name in mutations. + EdgeRoles = "roles" // Table holds the table name of the authtokens in the database. Table = "auth_tokens" // UserTable is the table that holds the user relation/edge. @@ -32,6 +34,13 @@ const ( UserInverseTable = "users" // UserColumn is the table column denoting the user relation/edge. UserColumn = "user_auth_tokens" + // RolesTable is the table that holds the roles relation/edge. + RolesTable = "auth_roles" + // RolesInverseTable is the table name for the AuthRoles entity. + // It exists in this package in order to avoid circular dependency with the "authroles" package. + RolesInverseTable = "auth_roles" + // RolesColumn is the table column denoting the roles relation/edge. + RolesColumn = "auth_tokens_roles" ) // Columns holds all SQL columns for authtokens fields. diff --git a/backend/internal/data/ent/authtokens/where.go b/backend/internal/data/ent/authtokens/where.go index 5ef7df9..68bcc5f 100644 --- a/backend/internal/data/ent/authtokens/where.go +++ b/backend/internal/data/ent/authtokens/where.go @@ -394,6 +394,34 @@ func HasUserWith(preds ...predicate.User) predicate.AuthTokens { }) } +// HasRoles applies the HasEdge predicate on the "roles" edge. +func HasRoles() predicate.AuthTokens { + return predicate.AuthTokens(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(RolesTable, FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, RolesTable, RolesColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasRolesWith applies the HasEdge predicate on the "roles" edge with a given conditions (other predicates). +func HasRolesWith(preds ...predicate.AuthRoles) predicate.AuthTokens { + return predicate.AuthTokens(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(RolesInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, RolesTable, RolesColumn), + ) + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.AuthTokens) predicate.AuthTokens { return predicate.AuthTokens(func(s *sql.Selector) { diff --git a/backend/internal/data/ent/authtokens_create.go b/backend/internal/data/ent/authtokens_create.go index d4fd34b..9100ed5 100644 --- a/backend/internal/data/ent/authtokens_create.go +++ b/backend/internal/data/ent/authtokens_create.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/user" ) @@ -103,6 +104,25 @@ func (atc *AuthTokensCreate) SetUser(u *User) *AuthTokensCreate { return atc.SetUserID(u.ID) } +// SetRolesID sets the "roles" edge to the AuthRoles entity by ID. +func (atc *AuthTokensCreate) SetRolesID(id int) *AuthTokensCreate { + atc.mutation.SetRolesID(id) + return atc +} + +// SetNillableRolesID sets the "roles" edge to the AuthRoles entity by ID if the given value is not nil. +func (atc *AuthTokensCreate) SetNillableRolesID(id *int) *AuthTokensCreate { + if id != nil { + atc = atc.SetRolesID(*id) + } + return atc +} + +// SetRoles sets the "roles" edge to the AuthRoles entity. +func (atc *AuthTokensCreate) SetRoles(a *AuthRoles) *AuthTokensCreate { + return atc.SetRolesID(a.ID) +} + // Mutation returns the AuthTokensMutation object of the builder. func (atc *AuthTokensCreate) Mutation() *AuthTokensMutation { return atc.mutation @@ -284,6 +304,25 @@ func (atc *AuthTokensCreate) createSpec() (*AuthTokens, *sqlgraph.CreateSpec) { _node.user_auth_tokens = &nodes[0] _spec.Edges = append(_spec.Edges, edge) } + if nodes := atc.mutation.RolesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: authtokens.RolesTable, + Columns: []string{authtokens.RolesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: authroles.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/backend/internal/data/ent/authtokens_query.go b/backend/internal/data/ent/authtokens_query.go index cf59e41..7020edb 100644 --- a/backend/internal/data/ent/authtokens_query.go +++ b/backend/internal/data/ent/authtokens_query.go @@ -4,6 +4,7 @@ package ent import ( "context" + "database/sql/driver" "fmt" "math" @@ -11,6 +12,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/user" @@ -26,6 +28,7 @@ type AuthTokensQuery struct { fields []string predicates []predicate.AuthTokens withUser *UserQuery + withRoles *AuthRolesQuery withFKs bool // intermediate query (i.e. traversal path). sql *sql.Selector @@ -85,6 +88,28 @@ func (atq *AuthTokensQuery) QueryUser() *UserQuery { return query } +// QueryRoles chains the current query on the "roles" edge. +func (atq *AuthTokensQuery) QueryRoles() *AuthRolesQuery { + query := &AuthRolesQuery{config: atq.config} + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := atq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := atq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(authtokens.Table, authtokens.FieldID, selector), + sqlgraph.To(authroles.Table, authroles.FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, authtokens.RolesTable, authtokens.RolesColumn), + ) + fromU = sqlgraph.SetNeighbors(atq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first AuthTokens entity from the query. // Returns a *NotFoundError when no AuthTokens was found. func (atq *AuthTokensQuery) First(ctx context.Context) (*AuthTokens, error) { @@ -267,6 +292,7 @@ func (atq *AuthTokensQuery) Clone() *AuthTokensQuery { order: append([]OrderFunc{}, atq.order...), predicates: append([]predicate.AuthTokens{}, atq.predicates...), withUser: atq.withUser.Clone(), + withRoles: atq.withRoles.Clone(), // clone intermediate query. sql: atq.sql.Clone(), path: atq.path, @@ -285,6 +311,17 @@ func (atq *AuthTokensQuery) WithUser(opts ...func(*UserQuery)) *AuthTokensQuery return atq } +// WithRoles tells the query-builder to eager-load the nodes that are connected to +// the "roles" edge. The optional arguments are used to configure the query builder of the edge. +func (atq *AuthTokensQuery) WithRoles(opts ...func(*AuthRolesQuery)) *AuthTokensQuery { + query := &AuthRolesQuery{config: atq.config} + for _, opt := range opts { + opt(query) + } + atq.withRoles = query + return atq +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -359,8 +396,9 @@ func (atq *AuthTokensQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]* nodes = []*AuthTokens{} withFKs = atq.withFKs _spec = atq.querySpec() - loadedTypes = [1]bool{ + loadedTypes = [2]bool{ atq.withUser != nil, + atq.withRoles != nil, } ) if atq.withUser != nil { @@ -393,6 +431,12 @@ func (atq *AuthTokensQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]* return nil, err } } + if query := atq.withRoles; query != nil { + if err := atq.loadRoles(ctx, query, nodes, nil, + func(n *AuthTokens, e *AuthRoles) { n.Edges.Roles = e }); err != nil { + return nil, err + } + } return nodes, nil } @@ -425,6 +469,34 @@ func (atq *AuthTokensQuery) loadUser(ctx context.Context, query *UserQuery, node } return nil } +func (atq *AuthTokensQuery) loadRoles(ctx context.Context, query *AuthRolesQuery, nodes []*AuthTokens, init func(*AuthTokens), assign func(*AuthTokens, *AuthRoles)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*AuthTokens) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + } + query.withFKs = true + query.Where(predicate.AuthRoles(func(s *sql.Selector) { + s.Where(sql.InValues(authtokens.RolesColumn, fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.auth_tokens_roles + if fk == nil { + return fmt.Errorf(`foreign-key "auth_tokens_roles" is nil for node %v`, n.ID) + } + node, ok := nodeids[*fk] + if !ok { + return fmt.Errorf(`unexpected foreign-key "auth_tokens_roles" returned %v for node %v`, *fk, n.ID) + } + assign(node, n) + } + return nil +} func (atq *AuthTokensQuery) sqlCount(ctx context.Context) (int, error) { _spec := atq.querySpec() diff --git a/backend/internal/data/ent/authtokens_update.go b/backend/internal/data/ent/authtokens_update.go index df374f2..b218d4c 100644 --- a/backend/internal/data/ent/authtokens_update.go +++ b/backend/internal/data/ent/authtokens_update.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/user" @@ -75,6 +76,25 @@ func (atu *AuthTokensUpdate) SetUser(u *User) *AuthTokensUpdate { return atu.SetUserID(u.ID) } +// SetRolesID sets the "roles" edge to the AuthRoles entity by ID. +func (atu *AuthTokensUpdate) SetRolesID(id int) *AuthTokensUpdate { + atu.mutation.SetRolesID(id) + return atu +} + +// SetNillableRolesID sets the "roles" edge to the AuthRoles entity by ID if the given value is not nil. +func (atu *AuthTokensUpdate) SetNillableRolesID(id *int) *AuthTokensUpdate { + if id != nil { + atu = atu.SetRolesID(*id) + } + return atu +} + +// SetRoles sets the "roles" edge to the AuthRoles entity. +func (atu *AuthTokensUpdate) SetRoles(a *AuthRoles) *AuthTokensUpdate { + return atu.SetRolesID(a.ID) +} + // Mutation returns the AuthTokensMutation object of the builder. func (atu *AuthTokensUpdate) Mutation() *AuthTokensMutation { return atu.mutation @@ -86,6 +106,12 @@ func (atu *AuthTokensUpdate) ClearUser() *AuthTokensUpdate { return atu } +// ClearRoles clears the "roles" edge to the AuthRoles entity. +func (atu *AuthTokensUpdate) ClearRoles() *AuthTokensUpdate { + atu.mutation.ClearRoles() + return atu +} + // Save executes the query and returns the number of nodes affected by the update operation. func (atu *AuthTokensUpdate) Save(ctx context.Context) (int, error) { var ( @@ -211,6 +237,41 @@ func (atu *AuthTokensUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if atu.mutation.RolesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: authtokens.RolesTable, + Columns: []string{authtokens.RolesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: authroles.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := atu.mutation.RolesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: authtokens.RolesTable, + Columns: []string{authtokens.RolesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: authroles.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if n, err = sqlgraph.UpdateNodes(ctx, atu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{authtokens.Label} @@ -275,6 +336,25 @@ func (atuo *AuthTokensUpdateOne) SetUser(u *User) *AuthTokensUpdateOne { return atuo.SetUserID(u.ID) } +// SetRolesID sets the "roles" edge to the AuthRoles entity by ID. +func (atuo *AuthTokensUpdateOne) SetRolesID(id int) *AuthTokensUpdateOne { + atuo.mutation.SetRolesID(id) + return atuo +} + +// SetNillableRolesID sets the "roles" edge to the AuthRoles entity by ID if the given value is not nil. +func (atuo *AuthTokensUpdateOne) SetNillableRolesID(id *int) *AuthTokensUpdateOne { + if id != nil { + atuo = atuo.SetRolesID(*id) + } + return atuo +} + +// SetRoles sets the "roles" edge to the AuthRoles entity. +func (atuo *AuthTokensUpdateOne) SetRoles(a *AuthRoles) *AuthTokensUpdateOne { + return atuo.SetRolesID(a.ID) +} + // Mutation returns the AuthTokensMutation object of the builder. func (atuo *AuthTokensUpdateOne) Mutation() *AuthTokensMutation { return atuo.mutation @@ -286,6 +366,12 @@ func (atuo *AuthTokensUpdateOne) ClearUser() *AuthTokensUpdateOne { return atuo } +// ClearRoles clears the "roles" edge to the AuthRoles entity. +func (atuo *AuthTokensUpdateOne) ClearRoles() *AuthTokensUpdateOne { + atuo.mutation.ClearRoles() + return atuo +} + // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (atuo *AuthTokensUpdateOne) Select(field string, fields ...string) *AuthTokensUpdateOne { @@ -441,6 +527,41 @@ func (atuo *AuthTokensUpdateOne) sqlSave(ctx context.Context) (_node *AuthTokens } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if atuo.mutation.RolesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: authtokens.RolesTable, + Columns: []string{authtokens.RolesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: authroles.FieldID, + }, + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := atuo.mutation.RolesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2O, + Inverse: false, + Table: authtokens.RolesTable, + Columns: []string{authtokens.RolesColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: &sqlgraph.FieldSpec{ + Type: field.TypeInt, + Column: authroles.FieldID, + }, + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &AuthTokens{config: atuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/backend/internal/data/ent/client.go b/backend/internal/data/ent/client.go index 605502c..67a57ef 100644 --- a/backend/internal/data/ent/client.go +++ b/backend/internal/data/ent/client.go @@ -12,6 +12,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/data/ent/migrate" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken" @@ -35,6 +36,8 @@ type Client struct { Schema *migrate.Schema // Attachment is the client for interacting with the Attachment builders. Attachment *AttachmentClient + // AuthRoles is the client for interacting with the AuthRoles builders. + AuthRoles *AuthRolesClient // AuthTokens is the client for interacting with the AuthTokens builders. AuthTokens *AuthTokensClient // Document is the client for interacting with the Document builders. @@ -69,6 +72,7 @@ func NewClient(opts ...Option) *Client { func (c *Client) init() { c.Schema = migrate.NewSchema(c.driver) c.Attachment = NewAttachmentClient(c.config) + c.AuthRoles = NewAuthRolesClient(c.config) c.AuthTokens = NewAuthTokensClient(c.config) c.Document = NewDocumentClient(c.config) c.DocumentToken = NewDocumentTokenClient(c.config) @@ -113,6 +117,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { ctx: ctx, config: cfg, Attachment: NewAttachmentClient(cfg), + AuthRoles: NewAuthRolesClient(cfg), AuthTokens: NewAuthTokensClient(cfg), Document: NewDocumentClient(cfg), DocumentToken: NewDocumentTokenClient(cfg), @@ -143,6 +148,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) ctx: ctx, config: cfg, Attachment: NewAttachmentClient(cfg), + AuthRoles: NewAuthRolesClient(cfg), AuthTokens: NewAuthTokensClient(cfg), Document: NewDocumentClient(cfg), DocumentToken: NewDocumentTokenClient(cfg), @@ -182,6 +188,7 @@ func (c *Client) Close() error { // In order to add hooks to a specific client, call: `client.Node.Use(...)`. func (c *Client) Use(hooks ...Hook) { c.Attachment.Use(hooks...) + c.AuthRoles.Use(hooks...) c.AuthTokens.Use(hooks...) c.Document.Use(hooks...) c.DocumentToken.Use(hooks...) @@ -316,6 +323,112 @@ func (c *AttachmentClient) Hooks() []Hook { return c.hooks.Attachment } +// AuthRolesClient is a client for the AuthRoles schema. +type AuthRolesClient struct { + config +} + +// NewAuthRolesClient returns a client for the AuthRoles from the given config. +func NewAuthRolesClient(c config) *AuthRolesClient { + return &AuthRolesClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `authroles.Hooks(f(g(h())))`. +func (c *AuthRolesClient) Use(hooks ...Hook) { + c.hooks.AuthRoles = append(c.hooks.AuthRoles, hooks...) +} + +// Create returns a builder for creating a AuthRoles entity. +func (c *AuthRolesClient) Create() *AuthRolesCreate { + mutation := newAuthRolesMutation(c.config, OpCreate) + return &AuthRolesCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of AuthRoles entities. +func (c *AuthRolesClient) CreateBulk(builders ...*AuthRolesCreate) *AuthRolesCreateBulk { + return &AuthRolesCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for AuthRoles. +func (c *AuthRolesClient) Update() *AuthRolesUpdate { + mutation := newAuthRolesMutation(c.config, OpUpdate) + return &AuthRolesUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *AuthRolesClient) UpdateOne(ar *AuthRoles) *AuthRolesUpdateOne { + mutation := newAuthRolesMutation(c.config, OpUpdateOne, withAuthRoles(ar)) + return &AuthRolesUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *AuthRolesClient) UpdateOneID(id int) *AuthRolesUpdateOne { + mutation := newAuthRolesMutation(c.config, OpUpdateOne, withAuthRolesID(id)) + return &AuthRolesUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for AuthRoles. +func (c *AuthRolesClient) Delete() *AuthRolesDelete { + mutation := newAuthRolesMutation(c.config, OpDelete) + return &AuthRolesDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *AuthRolesClient) DeleteOne(ar *AuthRoles) *AuthRolesDeleteOne { + return c.DeleteOneID(ar.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *AuthRolesClient) DeleteOneID(id int) *AuthRolesDeleteOne { + builder := c.Delete().Where(authroles.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &AuthRolesDeleteOne{builder} +} + +// Query returns a query builder for AuthRoles. +func (c *AuthRolesClient) Query() *AuthRolesQuery { + return &AuthRolesQuery{ + config: c.config, + } +} + +// Get returns a AuthRoles entity by its id. +func (c *AuthRolesClient) Get(ctx context.Context, id int) (*AuthRoles, error) { + return c.Query().Where(authroles.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *AuthRolesClient) GetX(ctx context.Context, id int) *AuthRoles { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryToken queries the token edge of a AuthRoles. +func (c *AuthRolesClient) QueryToken(ar *AuthRoles) *AuthTokensQuery { + query := &AuthTokensQuery{config: c.config} + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := ar.ID + step := sqlgraph.NewStep( + sqlgraph.From(authroles.Table, authroles.FieldID, id), + sqlgraph.To(authtokens.Table, authtokens.FieldID), + sqlgraph.Edge(sqlgraph.O2O, true, authroles.TokenTable, authroles.TokenColumn), + ) + fromV = sqlgraph.Neighbors(ar.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *AuthRolesClient) Hooks() []Hook { + return c.hooks.AuthRoles +} + // AuthTokensClient is a client for the AuthTokens schema. type AuthTokensClient struct { config @@ -417,6 +530,22 @@ func (c *AuthTokensClient) QueryUser(at *AuthTokens) *UserQuery { return query } +// QueryRoles queries the roles edge of a AuthTokens. +func (c *AuthTokensClient) QueryRoles(at *AuthTokens) *AuthRolesQuery { + query := &AuthRolesQuery{config: c.config} + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := at.ID + step := sqlgraph.NewStep( + sqlgraph.From(authtokens.Table, authtokens.FieldID, id), + sqlgraph.To(authroles.Table, authroles.FieldID), + sqlgraph.Edge(sqlgraph.O2O, false, authtokens.RolesTable, authtokens.RolesColumn), + ) + fromV = sqlgraph.Neighbors(at.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *AuthTokensClient) Hooks() []Hook { return c.hooks.AuthTokens diff --git a/backend/internal/data/ent/config.go b/backend/internal/data/ent/config.go index da76f30..e3ce09d 100644 --- a/backend/internal/data/ent/config.go +++ b/backend/internal/data/ent/config.go @@ -25,6 +25,7 @@ type config struct { // hooks per client, for fast access. type hooks struct { Attachment []ent.Hook + AuthRoles []ent.Hook AuthTokens []ent.Hook Document []ent.Hook DocumentToken []ent.Hook diff --git a/backend/internal/data/ent/ent.go b/backend/internal/data/ent/ent.go index 61f3d4e..0731b14 100644 --- a/backend/internal/data/ent/ent.go +++ b/backend/internal/data/ent/ent.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken" @@ -42,6 +43,7 @@ type OrderFunc func(*sql.Selector) func columnChecker(table string) func(string) error { checks := map[string]func(string) bool{ attachment.Table: attachment.ValidColumn, + authroles.Table: authroles.ValidColumn, authtokens.Table: authtokens.ValidColumn, document.Table: document.ValidColumn, documenttoken.Table: documenttoken.ValidColumn, diff --git a/backend/internal/data/ent/hook/hook.go b/backend/internal/data/ent/hook/hook.go index c0a7378..49d24c3 100644 --- a/backend/internal/data/ent/hook/hook.go +++ b/backend/internal/data/ent/hook/hook.go @@ -22,6 +22,19 @@ func (f AttachmentFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, return f(ctx, mv) } +// The AuthRolesFunc type is an adapter to allow the use of ordinary +// function as AuthRoles mutator. +type AuthRolesFunc func(context.Context, *ent.AuthRolesMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f AuthRolesFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + mv, ok := m.(*ent.AuthRolesMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AuthRolesMutation", m) + } + return f(ctx, mv) +} + // The AuthTokensFunc type is an adapter to allow the use of ordinary // function as AuthTokens mutator. type AuthTokensFunc func(context.Context, *ent.AuthTokensMutation) (ent.Value, error) diff --git a/backend/internal/data/ent/migrate/schema.go b/backend/internal/data/ent/migrate/schema.go index 81534c4..10beaa4 100644 --- a/backend/internal/data/ent/migrate/schema.go +++ b/backend/internal/data/ent/migrate/schema.go @@ -37,6 +37,26 @@ var ( }, }, } + // AuthRolesColumns holds the columns for the "auth_roles" table. + AuthRolesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "role", Type: field.TypeEnum, Enums: []string{"admin", "user", "attachments"}, Default: "user"}, + {Name: "auth_tokens_roles", Type: field.TypeUUID, Unique: true, Nullable: true}, + } + // AuthRolesTable holds the schema information for the "auth_roles" table. + AuthRolesTable = &schema.Table{ + Name: "auth_roles", + Columns: AuthRolesColumns, + PrimaryKey: []*schema.Column{AuthRolesColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "auth_roles_auth_tokens_roles", + Columns: []*schema.Column{AuthRolesColumns[2]}, + RefColumns: []*schema.Column{AuthTokensColumns[0]}, + OnDelete: schema.SetNull, + }, + }, + } // AuthTokensColumns holds the columns for the "auth_tokens" table. AuthTokensColumns = []*schema.Column{ {Name: "id", Type: field.TypeUUID}, @@ -385,6 +405,7 @@ var ( // Tables holds all the tables in the schema. Tables = []*schema.Table{ AttachmentsTable, + AuthRolesTable, AuthTokensTable, DocumentsTable, DocumentTokensTable, @@ -402,6 +423,7 @@ var ( func init() { AttachmentsTable.ForeignKeys[0].RefTable = DocumentsTable AttachmentsTable.ForeignKeys[1].RefTable = ItemsTable + AuthRolesTable.ForeignKeys[0].RefTable = AuthTokensTable AuthTokensTable.ForeignKeys[0].RefTable = UsersTable DocumentsTable.ForeignKeys[0].RefTable = GroupsTable DocumentTokensTable.ForeignKeys[0].RefTable = DocumentsTable diff --git a/backend/internal/data/ent/mutation.go b/backend/internal/data/ent/mutation.go index 2f1ebdc..ea9a441 100644 --- a/backend/internal/data/ent/mutation.go +++ b/backend/internal/data/ent/mutation.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/document" "github.com/hay-kot/homebox/backend/internal/data/ent/documenttoken" @@ -36,6 +37,7 @@ const ( // Node types. TypeAttachment = "Attachment" + TypeAuthRoles = "AuthRoles" TypeAuthTokens = "AuthTokens" TypeDocument = "Document" TypeDocumentToken = "DocumentToken" @@ -599,6 +601,384 @@ func (m *AttachmentMutation) ResetEdge(name string) error { return fmt.Errorf("unknown Attachment edge %s", name) } +// AuthRolesMutation represents an operation that mutates the AuthRoles nodes in the graph. +type AuthRolesMutation struct { + config + op Op + typ string + id *int + role *authroles.Role + clearedFields map[string]struct{} + token *uuid.UUID + clearedtoken bool + done bool + oldValue func(context.Context) (*AuthRoles, error) + predicates []predicate.AuthRoles +} + +var _ ent.Mutation = (*AuthRolesMutation)(nil) + +// authrolesOption allows management of the mutation configuration using functional options. +type authrolesOption func(*AuthRolesMutation) + +// newAuthRolesMutation creates new mutation for the AuthRoles entity. +func newAuthRolesMutation(c config, op Op, opts ...authrolesOption) *AuthRolesMutation { + m := &AuthRolesMutation{ + config: c, + op: op, + typ: TypeAuthRoles, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withAuthRolesID sets the ID field of the mutation. +func withAuthRolesID(id int) authrolesOption { + return func(m *AuthRolesMutation) { + var ( + err error + once sync.Once + value *AuthRoles + ) + m.oldValue = func(ctx context.Context) (*AuthRoles, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().AuthRoles.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withAuthRoles sets the old AuthRoles of the mutation. +func withAuthRoles(node *AuthRoles) authrolesOption { + return func(m *AuthRolesMutation) { + m.oldValue = func(context.Context) (*AuthRoles, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m AuthRolesMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m AuthRolesMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *AuthRolesMutation) ID() (id int, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *AuthRolesMutation) IDs(ctx context.Context) ([]int, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().AuthRoles.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetRole sets the "role" field. +func (m *AuthRolesMutation) SetRole(a authroles.Role) { + m.role = &a +} + +// Role returns the value of the "role" field in the mutation. +func (m *AuthRolesMutation) Role() (r authroles.Role, exists bool) { + v := m.role + if v == nil { + return + } + return *v, true +} + +// OldRole returns the old "role" field's value of the AuthRoles entity. +// If the AuthRoles object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *AuthRolesMutation) OldRole(ctx context.Context) (v authroles.Role, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldRole is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldRole requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldRole: %w", err) + } + return oldValue.Role, nil +} + +// ResetRole resets all changes to the "role" field. +func (m *AuthRolesMutation) ResetRole() { + m.role = nil +} + +// SetTokenID sets the "token" edge to the AuthTokens entity by id. +func (m *AuthRolesMutation) SetTokenID(id uuid.UUID) { + m.token = &id +} + +// ClearToken clears the "token" edge to the AuthTokens entity. +func (m *AuthRolesMutation) ClearToken() { + m.clearedtoken = true +} + +// TokenCleared reports if the "token" edge to the AuthTokens entity was cleared. +func (m *AuthRolesMutation) TokenCleared() bool { + return m.clearedtoken +} + +// TokenID returns the "token" edge ID in the mutation. +func (m *AuthRolesMutation) TokenID() (id uuid.UUID, exists bool) { + if m.token != nil { + return *m.token, true + } + return +} + +// TokenIDs returns the "token" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// TokenID instead. It exists only for internal usage by the builders. +func (m *AuthRolesMutation) TokenIDs() (ids []uuid.UUID) { + if id := m.token; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetToken resets all changes to the "token" edge. +func (m *AuthRolesMutation) ResetToken() { + m.token = nil + m.clearedtoken = false +} + +// Where appends a list predicates to the AuthRolesMutation builder. +func (m *AuthRolesMutation) Where(ps ...predicate.AuthRoles) { + m.predicates = append(m.predicates, ps...) +} + +// Op returns the operation name. +func (m *AuthRolesMutation) Op() Op { + return m.op +} + +// Type returns the node type of this mutation (AuthRoles). +func (m *AuthRolesMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *AuthRolesMutation) Fields() []string { + fields := make([]string, 0, 1) + if m.role != nil { + fields = append(fields, authroles.FieldRole) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *AuthRolesMutation) Field(name string) (ent.Value, bool) { + switch name { + case authroles.FieldRole: + return m.Role() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *AuthRolesMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case authroles.FieldRole: + return m.OldRole(ctx) + } + return nil, fmt.Errorf("unknown AuthRoles field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AuthRolesMutation) SetField(name string, value ent.Value) error { + switch name { + case authroles.FieldRole: + v, ok := value.(authroles.Role) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetRole(v) + return nil + } + return fmt.Errorf("unknown AuthRoles field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *AuthRolesMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *AuthRolesMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *AuthRolesMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown AuthRoles numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *AuthRolesMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *AuthRolesMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *AuthRolesMutation) ClearField(name string) error { + return fmt.Errorf("unknown AuthRoles nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *AuthRolesMutation) ResetField(name string) error { + switch name { + case authroles.FieldRole: + m.ResetRole() + return nil + } + return fmt.Errorf("unknown AuthRoles field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *AuthRolesMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.token != nil { + edges = append(edges, authroles.EdgeToken) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *AuthRolesMutation) AddedIDs(name string) []ent.Value { + switch name { + case authroles.EdgeToken: + if id := m.token; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *AuthRolesMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *AuthRolesMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *AuthRolesMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedtoken { + edges = append(edges, authroles.EdgeToken) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *AuthRolesMutation) EdgeCleared(name string) bool { + switch name { + case authroles.EdgeToken: + return m.clearedtoken + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *AuthRolesMutation) ClearEdge(name string) error { + switch name { + case authroles.EdgeToken: + m.ClearToken() + return nil + } + return fmt.Errorf("unknown AuthRoles unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *AuthRolesMutation) ResetEdge(name string) error { + switch name { + case authroles.EdgeToken: + m.ResetToken() + return nil + } + return fmt.Errorf("unknown AuthRoles edge %s", name) +} + // AuthTokensMutation represents an operation that mutates the AuthTokens nodes in the graph. type AuthTokensMutation struct { config @@ -612,6 +992,8 @@ type AuthTokensMutation struct { clearedFields map[string]struct{} user *uuid.UUID cleareduser bool + roles *int + clearedroles bool done bool oldValue func(context.Context) (*AuthTokens, error) predicates []predicate.AuthTokens @@ -904,6 +1286,45 @@ func (m *AuthTokensMutation) ResetUser() { m.cleareduser = false } +// SetRolesID sets the "roles" edge to the AuthRoles entity by id. +func (m *AuthTokensMutation) SetRolesID(id int) { + m.roles = &id +} + +// ClearRoles clears the "roles" edge to the AuthRoles entity. +func (m *AuthTokensMutation) ClearRoles() { + m.clearedroles = true +} + +// RolesCleared reports if the "roles" edge to the AuthRoles entity was cleared. +func (m *AuthTokensMutation) RolesCleared() bool { + return m.clearedroles +} + +// RolesID returns the "roles" edge ID in the mutation. +func (m *AuthTokensMutation) RolesID() (id int, exists bool) { + if m.roles != nil { + return *m.roles, true + } + return +} + +// RolesIDs returns the "roles" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// RolesID instead. It exists only for internal usage by the builders. +func (m *AuthTokensMutation) RolesIDs() (ids []int) { + if id := m.roles; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetRoles resets all changes to the "roles" edge. +func (m *AuthTokensMutation) ResetRoles() { + m.roles = nil + m.clearedroles = false +} + // Where appends a list predicates to the AuthTokensMutation builder. func (m *AuthTokensMutation) Where(ps ...predicate.AuthTokens) { m.predicates = append(m.predicates, ps...) @@ -1073,10 +1494,13 @@ func (m *AuthTokensMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *AuthTokensMutation) AddedEdges() []string { - edges := make([]string, 0, 1) + edges := make([]string, 0, 2) if m.user != nil { edges = append(edges, authtokens.EdgeUser) } + if m.roles != nil { + edges = append(edges, authtokens.EdgeRoles) + } return edges } @@ -1088,13 +1512,17 @@ func (m *AuthTokensMutation) AddedIDs(name string) []ent.Value { if id := m.user; id != nil { return []ent.Value{*id} } + case authtokens.EdgeRoles: + if id := m.roles; id != nil { + return []ent.Value{*id} + } } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *AuthTokensMutation) RemovedEdges() []string { - edges := make([]string, 0, 1) + edges := make([]string, 0, 2) return edges } @@ -1106,10 +1534,13 @@ func (m *AuthTokensMutation) RemovedIDs(name string) []ent.Value { // ClearedEdges returns all edge names that were cleared in this mutation. func (m *AuthTokensMutation) ClearedEdges() []string { - edges := make([]string, 0, 1) + edges := make([]string, 0, 2) if m.cleareduser { edges = append(edges, authtokens.EdgeUser) } + if m.clearedroles { + edges = append(edges, authtokens.EdgeRoles) + } return edges } @@ -1119,6 +1550,8 @@ func (m *AuthTokensMutation) EdgeCleared(name string) bool { switch name { case authtokens.EdgeUser: return m.cleareduser + case authtokens.EdgeRoles: + return m.clearedroles } return false } @@ -1130,6 +1563,9 @@ func (m *AuthTokensMutation) ClearEdge(name string) error { case authtokens.EdgeUser: m.ClearUser() return nil + case authtokens.EdgeRoles: + m.ClearRoles() + return nil } return fmt.Errorf("unknown AuthTokens unique edge %s", name) } @@ -1141,6 +1577,9 @@ func (m *AuthTokensMutation) ResetEdge(name string) error { case authtokens.EdgeUser: m.ResetUser() return nil + case authtokens.EdgeRoles: + m.ResetRoles() + return nil } return fmt.Errorf("unknown AuthTokens edge %s", name) } diff --git a/backend/internal/data/ent/predicate/predicate.go b/backend/internal/data/ent/predicate/predicate.go index 12b1f44..21a0a71 100644 --- a/backend/internal/data/ent/predicate/predicate.go +++ b/backend/internal/data/ent/predicate/predicate.go @@ -9,6 +9,9 @@ import ( // Attachment is the predicate function for attachment builders. type Attachment func(*sql.Selector) +// AuthRoles is the predicate function for authroles builders. +type AuthRoles func(*sql.Selector) + // AuthTokens is the predicate function for authtokens builders. type AuthTokens func(*sql.Selector) diff --git a/backend/internal/data/ent/runtime.go b/backend/internal/data/ent/runtime.go index 25a7680..a9edda6 100644 --- a/backend/internal/data/ent/runtime.go +++ b/backend/internal/data/ent/runtime.go @@ -43,6 +43,8 @@ func init() { attachmentDescID := attachmentMixinFields0[0].Descriptor() // attachment.DefaultID holds the default value on creation for the id field. attachment.DefaultID = attachmentDescID.Default.(func() uuid.UUID) + authrolesFields := schema.AuthRoles{}.Fields() + _ = authrolesFields authtokensMixin := schema.AuthTokens{}.Mixin() authtokensMixinFields0 := authtokensMixin[0].Fields() _ = authtokensMixinFields0 diff --git a/backend/internal/data/ent/schema/auth_roles.go b/backend/internal/data/ent/schema/auth_roles.go new file mode 100644 index 0000000..5333eb3 --- /dev/null +++ b/backend/internal/data/ent/schema/auth_roles.go @@ -0,0 +1,34 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" +) + +// AuthRoles holds the schema definition for the AuthRoles entity. +type AuthRoles struct { + ent.Schema +} + +// Fields of the AuthRoles. +func (AuthRoles) Fields() []ent.Field { + return []ent.Field{ + field.Enum("role"). + Default("user"). + Values( + "admin", // can do everything - currently unused + "user", // default login role + "attachments", // Read Attachments + ), + } +} + +// Edges of the AuthRoles. +func (AuthRoles) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("token", AuthTokens.Type). + Ref("roles"). + Unique(), + } +} diff --git a/backend/internal/data/ent/schema/auth_tokens.go b/backend/internal/data/ent/schema/auth_tokens.go index 0cfd4d1..e29b79a 100644 --- a/backend/internal/data/ent/schema/auth_tokens.go +++ b/backend/internal/data/ent/schema/auth_tokens.go @@ -37,6 +37,8 @@ func (AuthTokens) Edges() []ent.Edge { edge.From("user", User.Type). Ref("auth_tokens"). Unique(), + edge.To("roles", AuthRoles.Type). + Unique(), } } diff --git a/backend/internal/data/ent/tx.go b/backend/internal/data/ent/tx.go index b6ba4f9..8af5b01 100644 --- a/backend/internal/data/ent/tx.go +++ b/backend/internal/data/ent/tx.go @@ -14,6 +14,8 @@ type Tx struct { config // Attachment is the client for interacting with the Attachment builders. Attachment *AttachmentClient + // AuthRoles is the client for interacting with the AuthRoles builders. + AuthRoles *AuthRolesClient // AuthTokens is the client for interacting with the AuthTokens builders. AuthTokens *AuthTokensClient // Document is the client for interacting with the Document builders. @@ -166,6 +168,7 @@ func (tx *Tx) Client() *Client { func (tx *Tx) init() { tx.Attachment = NewAttachmentClient(tx.config) + tx.AuthRoles = NewAuthRolesClient(tx.config) tx.AuthTokens = NewAuthTokensClient(tx.config) tx.Document = NewDocumentClient(tx.config) tx.DocumentToken = NewDocumentTokenClient(tx.config) diff --git a/backend/internal/data/migrations/migrations/20221203053132_add_token_roles.sql b/backend/internal/data/migrations/migrations/20221203053132_add_token_roles.sql new file mode 100644 index 0000000..6f6d00a --- /dev/null +++ b/backend/internal/data/migrations/migrations/20221203053132_add_token_roles.sql @@ -0,0 +1,4 @@ +-- create "auth_roles" table +CREATE TABLE `auth_roles` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `role` text NOT NULL DEFAULT 'user', `auth_tokens_roles` uuid NULL, CONSTRAINT `auth_roles_auth_tokens_roles` FOREIGN KEY (`auth_tokens_roles`) REFERENCES `auth_tokens` (`id`) ON DELETE SET NULL); +-- create index "auth_roles_auth_tokens_roles_key" to table: "auth_roles" +CREATE UNIQUE INDEX `auth_roles_auth_tokens_roles_key` ON `auth_roles` (`auth_tokens_roles`); diff --git a/backend/internal/data/migrations/migrations/atlas.sum b/backend/internal/data/migrations/migrations/atlas.sum index d9c72fb..0c9927b 100644 --- a/backend/internal/data/migrations/migrations/atlas.sum +++ b/backend/internal/data/migrations/migrations/atlas.sum @@ -1,7 +1,8 @@ -h1:z1tbZ3fYByqxL78Z+ov8mfQVjXcwsZeEcT0i+2DZ8a8= +h1:oo2QbYbKkbf4oTfkRXqo9XGPp8S76j33WQvDZITv5s8= 20220929052825_init.sql h1:ZlCqm1wzjDmofeAcSX3jE4h4VcdTNGpRg2eabztDy9Q= 20221001210956_group_invitations.sql h1:YQKJFtE39wFOcRNbZQ/d+ZlHwrcfcsZlcv/pLEYdpjw= 20221009173029_add_user_roles.sql h1:vWmzAfgEWQeGk0Vn70zfVPCcfEZth3E0JcvyKTjpYyU= 20221020043305_allow_nesting_types.sql h1:4AyJpZ7l7SSJtJAQETYY802FHJ64ufYPJTqvwdiGn3M= 20221101041931_add_archived_field.sql h1:L2WxiOh1svRn817cNURgqnEQg6DIcodZ1twK4tvxW94= 20221113012312_add_asset_id_field.sql h1:DjD7e1PS8OfxGBWic8h0nO/X6CNnHEMqQjDCaaQ3M3Q= +20221203053132_add_token_roles.sql h1:wFTIh+KBoHfLfy/L0ZmJz4cNXKHdACG9ZK/yvVKjF0M= diff --git a/backend/internal/data/repo/repo_tokens.go b/backend/internal/data/repo/repo_tokens.go index 7d9115b..5820227 100644 --- a/backend/internal/data/repo/repo_tokens.go +++ b/backend/internal/data/repo/repo_tokens.go @@ -6,7 +6,10 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/data/ent" + "github.com/hay-kot/homebox/backend/internal/data/ent/authroles" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" + "github.com/hay-kot/homebox/backend/pkgs/hasher" + "github.com/hay-kot/homebox/backend/pkgs/set" ) type TokenRepository struct { @@ -47,9 +50,31 @@ func (r *TokenRepository) GetUserFromToken(ctx context.Context, token []byte) (U return mapUserOut(user), nil } -// Creates a token for a user -func (r *TokenRepository) CreateToken(ctx context.Context, createToken UserAuthTokenCreate) (UserAuthToken, error) { +func (r *TokenRepository) GetRoles(ctx context.Context, token string) (*set.Set[string], error) { + tokenHash := hasher.HashToken(token) + roles, err := r.db.AuthRoles. + Query(). + Where(authroles.HasTokenWith( + authtokens.Token(tokenHash), + )). + All(ctx) + + if err != nil { + return nil, err + } + + roleSet := set.Make[string](len(roles)) + + for _, role := range roles { + roleSet.Insert(role.Role.String()) + } + + return &roleSet, nil +} + +// Creates a token for a user +func (r *TokenRepository) CreateToken(ctx context.Context, createToken UserAuthTokenCreate, roles ...authroles.Role) (UserAuthToken, error) { dbToken, err := r.db.AuthTokens.Create(). SetToken(createToken.TokenHash). SetUserID(createToken.UserID). @@ -60,6 +85,17 @@ func (r *TokenRepository) CreateToken(ctx context.Context, createToken UserAuthT return UserAuthToken{}, err } + for _, role := range roles { + _, err := r.db.AuthRoles.Create(). + SetRole(role). + SetToken(dbToken). + Save(ctx) + + if err != nil { + return UserAuthToken{}, err + } + } + return UserAuthToken{ UserAuthTokenCreate: UserAuthTokenCreate{ TokenHash: dbToken.Token, diff --git a/backend/pkgs/set/set.go b/backend/pkgs/set/set.go index f2ffecc..b0918bb 100644 --- a/backend/pkgs/set/set.go +++ b/backend/pkgs/set/set.go @@ -8,6 +8,12 @@ type Set[T key] struct { mp map[T]struct{} } +func Make[T key](size int) Set[T] { + return Set[T]{ + mp: make(map[T]struct{}, size), + } +} + func New[T key](v ...T) Set[T] { mp := make(map[T]struct{}, len(v)) diff --git a/frontend/components/Item/AttachmentsList.vue b/frontend/components/Item/AttachmentsList.vue index f215256..ddad600 100644 --- a/frontend/components/Item/AttachmentsList.vue +++ b/frontend/components/Item/AttachmentsList.vue @@ -10,7 +10,12 @@ {{ attachment.document.title }}
- + + + + + +
@@ -31,25 +36,9 @@ }); const api = useUserApi(); - const toast = useNotifier(); - async function getAttachmentUrl(attachment: ItemAttachment) { - const url = await api.items.getAttachmentUrl(props.itemId, attachment.id); - if (!url) { - toast.error("Failed to get attachment url"); - return; - } - - if (!document) { - window.open(url, "_blank"); - return; - } - - const link = document.createElement("a"); - link.href = url; - link.target = "_blank"; - link.setAttribute("download", attachment.document.title); - link.click(); + function attachmentURL(attachmentId: string) { + return api.authURL(`/items/${props.itemId}/attachments/${attachmentId}`); } diff --git a/frontend/composables/use-api.ts b/frontend/composables/use-api.ts index 69da2ed..cd06588 100644 --- a/frontend/composables/use-api.ts +++ b/frontend/composables/use-api.ts @@ -43,5 +43,5 @@ export function useUserApi(): UserClient { requests.addResponseInterceptor(observer.handler); } - return new UserClient(requests); + return new UserClient(requests, authStore.attachmentToken); } diff --git a/frontend/lib/api/base/base-api.ts b/frontend/lib/api/base/base-api.ts index b48d10b..6db8951 100644 --- a/frontend/lib/api/base/base-api.ts +++ b/frontend/lib/api/base/base-api.ts @@ -27,9 +27,21 @@ export function parseDate(obj: T, keys: Array = []): T { export class BaseAPI { http: Requests; + attachmentToken: string; - constructor(requests: Requests) { + constructor(requests: Requests, attachmentToken = "") { this.http = requests; + this.attachmentToken = attachmentToken; + } + + // if a attachmentToken is present it will be added to URL as a query param + // this is done with a simple appending of the query param to the URL. If your + // URL already has a query param, this will not work. + authURL(url: string): string { + if (this.attachmentToken) { + return `/api/v1${url}?access_token=${this.attachmentToken}`; + } + return url; } /** diff --git a/frontend/lib/api/classes/items.ts b/frontend/lib/api/classes/items.ts index a97152d..ee18f01 100644 --- a/frontend/lib/api/classes/items.ts +++ b/frontend/lib/api/classes/items.ts @@ -1,13 +1,6 @@ import { BaseAPI, route } from "../base"; import { parseDate } from "../base/base-api"; -import { - ItemAttachmentToken, - ItemAttachmentUpdate, - ItemCreate, - ItemOut, - ItemSummary, - ItemUpdate, -} from "../types/data-contracts"; +import { ItemAttachmentUpdate, ItemCreate, ItemOut, ItemSummary, ItemUpdate } from "../types/data-contracts"; import { AttachmentTypes, PaginationResult } from "../types/non-generated"; export type ItemsQuery = { @@ -79,18 +72,6 @@ export class ItemsApi extends BaseAPI { }); } - async getAttachmentUrl(id: string, attachmentId: string): Promise { - const payload = await this.http.get({ - url: route(`/items/${id}/attachments/${attachmentId}`), - }); - - if (!payload.data) { - return ""; - } - - return route(`/items/${id}/attachments/download`, { token: payload.data.token }); - } - async deleteAttachment(id: string, attachmentId: string) { return await this.http.delete({ url: route(`/items/${id}/attachments/${attachmentId}`) }); } diff --git a/frontend/lib/api/types/data-contracts.ts b/frontend/lib/api/types/data-contracts.ts index 9829f14..464b8e7 100644 --- a/frontend/lib/api/types/data-contracts.ts +++ b/frontend/lib/api/types/data-contracts.ts @@ -324,6 +324,7 @@ export interface ItemAttachmentToken { } export interface TokenResponse { + attachmentToken: string; expiresAt: Date; token: string; } diff --git a/frontend/lib/api/user.ts b/frontend/lib/api/user.ts index 5c4940f..31538a8 100644 --- a/frontend/lib/api/user.ts +++ b/frontend/lib/api/user.ts @@ -15,8 +15,8 @@ export class UserClient extends BaseAPI { user: UserApi; actions: ActionsAPI; - constructor(requests: Requests) { - super(requests); + constructor(requests: Requests, attachmentToken: string) { + super(requests, attachmentToken); this.locations = new LocationsApi(requests); this.labels = new LabelsApi(requests); diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue index 44183c6..070fa05 100644 --- a/frontend/pages/index.vue +++ b/frontend/pages/index.vue @@ -105,6 +105,7 @@ authStore.$patch({ token: data.token, expires: data.expiresAt, + attachmentToken: data.attachmentToken, }); navigateTo("/home"); diff --git a/frontend/pages/item/[id]/index.vue b/frontend/pages/item/[id]/index.vue index e3d6afb..256ff0c 100644 --- a/frontend/pages/item/[id]/index.vue +++ b/frontend/pages/item/[id]/index.vue @@ -27,17 +27,32 @@ }); type FilteredAttachments = { - photos: ItemAttachment[]; attachments: ItemAttachment[]; warranty: ItemAttachment[]; manuals: ItemAttachment[]; receipts: ItemAttachment[]; }; + type Photo = { + src: string; + }; + + const photos = computed(() => { + return ( + item.value?.attachments.reduce((acc, cur) => { + if (cur.type === "photo") { + acc.push({ + src: api.authURL(`/items/${item.value.id}/attachments/${cur.id}`), + }); + } + return acc; + }, [] as Photo[]) || [] + ); + }); + const attachments = computed(() => { if (!item.value) { return { - photos: [], attachments: [], manuals: [], warranty: [], @@ -48,8 +63,9 @@ return item.value.attachments.reduce( (acc, attachment) => { if (attachment.type === "photo") { - acc.photos.push(attachment); - } else if (attachment.type === "warranty") { + return acc; + } + if (attachment.type === "warranty") { acc.warranty.push(attachment); } else if (attachment.type === "manual") { acc.manuals.push(attachment); @@ -61,7 +77,6 @@ return acc; }, { - photos: [] as ItemAttachment[], attachments: [] as ItemAttachment[], warranty: [] as ItemAttachment[], manuals: [] as ItemAttachment[], @@ -144,7 +159,6 @@ } return ( - attachments.value.photos.length > 0 || attachments.value.attachments.length > 0 || attachments.value.warranty.length > 0 || attachments.value.manuals.length > 0 || @@ -163,10 +177,6 @@ }); }; - if (attachments.value.photos.length > 0) { - push("Photos"); - } - if (attachments.value.attachments.length > 0) { push("Attachments"); } @@ -292,10 +302,43 @@ toast.success("Item deleted"); navigateTo("/home"); } + + const refDialog = ref(); + const dialoged = reactive({ + src: "", + }); + + function openDialog(img: Photo) { + refDialog.value.showModal(); + dialoged.src = img.src; + } + + function closeDialog() { + refDialog.value.close(); + } + + const refDialogBody = ref(); + onClickOutside(refDialogBody, () => { + closeDialog(); + }); - + + diff --git a/frontend/stores/auth.ts b/frontend/stores/auth.ts index a7e641f..9b72901 100644 --- a/frontend/stores/auth.ts +++ b/frontend/stores/auth.ts @@ -6,6 +6,7 @@ import { UserOut } from "~~/lib/api/types/data-contracts"; export const useAuthStore = defineStore("auth", { state: () => ({ token: useLocalStorage("pinia/auth/token", ""), + attachmentToken: useLocalStorage("pinia/auth/attachmentToken", ""), expires: useLocalStorage("pinia/auth/expires", ""), self: null as UserOut | null, }), @@ -27,6 +28,7 @@ export const useAuthStore = defineStore("auth", { const result = await api.user.logout(); this.token = ""; + this.attachmentToken = ""; this.expires = ""; this.self = null;