diff --git a/.github/workflows/partial-frontend.yaml b/.github/workflows/partial-frontend.yaml index f8494064..78071b91 100644 --- a/.github/workflows/partial-frontend.yaml +++ b/.github/workflows/partial-frontend.yaml @@ -13,9 +13,13 @@ jobs: with: fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - uses: pnpm/action-setup@v3.0.0 with: - version: 6.0.2 + version: 9 - name: Install dependencies run: pnpm install --shamefully-hoist @@ -46,18 +50,18 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.21" + go-version: "1.22" - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - uses: pnpm/action-setup@v3.0.0 with: - version: 6.0.2 + version: 9 - name: Install dependencies - run: pnpm install + run: pnpm install --shamefully-hoist working-directory: frontend - name: Run Integration Tests diff --git a/Taskfile.yml b/Taskfile.yml index 4d9c1aa2..f23c22f8 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -5,6 +5,11 @@ env: HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_pragma=busy_timeout=1000&_pragma=journal_mode=WAL&_fk=1 HBOX_OPTIONS_ALLOW_REGISTRATION: true UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure" + HBOX_MAILER_HOST: 127.0.0.1 + HBOX_MAILER_PORT: 1025 + HBOX_MAILER_USERNAME: c836555d57d205 + HBOX_MAILER_PASSWORD: 3ff2f9986f3cff + HBOX_MAILER_FROM: info@example.com tasks: setup: desc: Install development dependencies @@ -79,6 +84,12 @@ tasks: cmds: - go mod tidy + go:fmt: + desc: Runs go fmt on the backend + dir: backend + cmds: + - gofumpt -w . + go:lint: desc: Runs golangci-lint dir: backend diff --git a/backend/.golangci.yml b/backend/.golangci.yml index 8f631103..d4e432f4 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -1,8 +1,9 @@ run: timeout: 10m - skip-dirs: - - internal/data/ent.* linters-settings: + errcheck: + exclude-functions: + - (net/http.ResponseWriter).Write goconst: min-len: 5 min-occurrences: 5 @@ -71,4 +72,6 @@ linters: - sqlclosecheck issues: exclude-use-default: false - fix: true + fix: false + exclude-dirs: + - internal/data/ent.* diff --git a/backend/app/api/app.go b/backend/app/api/app.go index 5d285d3c..502d64a3 100644 --- a/backend/app/api/app.go +++ b/backend/app/api/app.go @@ -11,7 +11,7 @@ import ( type app struct { conf *config.Config - mailer mailer.Mailer + mailer *mailer.Mailer db *ent.Client repos *repo.AllRepos services *services.AllServices @@ -23,7 +23,7 @@ func new(conf *config.Config) *app { conf: conf, } - s.mailer = mailer.Mailer{ + s.mailer = &mailer.Mailer{ Host: s.conf.Mailer.Host, Port: s.conf.Mailer.Port, Username: s.conf.Mailer.Username, diff --git a/backend/app/api/demo.go b/backend/app/api/demo.go index 183e0e00..bf4b20db 100644 --- a/backend/app/api/demo.go +++ b/backend/app/api/demo.go @@ -40,20 +40,18 @@ func (a *app) SetupDemo() { _, err = a.services.User.RegisterUser(ctx, registration) if err != nil { log.Err(err).Msg("Failed to register demo user") - log.Fatal().Msg("Failed to setup demo") + log.Fatal().Msg("Failed to setup demo") // nolint } token, err := a.services.User.Login(ctx, registration.Email, registration.Password, false) if err != nil { log.Err(err).Msg("Failed to login demo user") log.Fatal().Msg("Failed to setup demo") - return } self, err := a.services.User.GetSelf(ctx, token.Raw) if err != nil { log.Err(err).Msg("Failed to get self") log.Fatal().Msg("Failed to setup demo") - return } _, err = a.services.Items.CsvImport(ctx, self.GroupID, strings.NewReader(csvText)) diff --git a/backend/app/api/handlers/v1/controller.go b/backend/app/api/handlers/v1/controller.go index eb602125..4f5e125b 100644 --- a/backend/app/api/handlers/v1/controller.go +++ b/backend/app/api/handlers/v1/controller.go @@ -153,7 +153,7 @@ func (ctrl *V1Controller) HandleCacheWS() errchain.HandlerFunc { m.HandleConnect(func(s *melody.Session) { auth := services.NewContext(s.Request.Context()) - s.Set("gid", auth.GID) + s.Set("gid", auth.GroupID) }) factory := func(e string) func(data any) { diff --git a/backend/app/api/handlers/v1/v1_ctrl_actions.go b/backend/app/api/handlers/v1/v1_ctrl_actions.go index 75f39a58..043df136 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_actions.go +++ b/backend/app/api/handlers/v1/v1_ctrl_actions.go @@ -20,7 +20,7 @@ func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int, return func(w http.ResponseWriter, r *http.Request) error { ctx := services.NewContext(r.Context()) - totalCompleted, err := fn(ctx, ctx.GID) + totalCompleted, err := fn(ctx, ctx.GroupID) if err != nil { log.Err(err).Str("action_ref", ref).Msg("failed to run action") return validate.NewRequestError(err, http.StatusInternalServerError) diff --git a/backend/app/api/handlers/v1/v1_ctrl_assets.go b/backend/app/api/handlers/v1/v1_ctrl_assets.go index 91e9a3c9..87b96a18 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_assets.go +++ b/backend/app/api/handlers/v1/v1_ctrl_assets.go @@ -52,7 +52,7 @@ func (ctrl *V1Controller) HandleAssetGet() errchain.HandlerFunc { } } - items, err := ctrl.repo.Items.QueryByAssetID(r.Context(), ctx.GID, repo.AssetID(assetID), int(page), int(pageSize)) + items, err := ctrl.repo.Items.QueryByAssetID(r.Context(), ctx.GroupID, repo.AssetID(assetID), int(page), int(pageSize)) if err != nil { log.Err(err).Msg("failed to get item") return validate.NewRequestError(err, http.StatusInternalServerError) diff --git a/backend/app/api/handlers/v1/v1_ctrl_group.go b/backend/app/api/handlers/v1/v1_ctrl_group.go index 69bc0249..c3c1625f 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_group.go +++ b/backend/app/api/handlers/v1/v1_ctrl_group.go @@ -35,7 +35,7 @@ type ( func (ctrl *V1Controller) HandleGroupGet() errchain.HandlerFunc { fn := func(r *http.Request) (repo.Group, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Groups.GroupByID(auth, auth.GID) + return ctrl.repo.Groups.GroupByID(auth, auth.GroupID) } return adapters.Command(fn, http.StatusOK) diff --git a/backend/app/api/handlers/v1/v1_ctrl_items.go b/backend/app/api/handlers/v1/v1_ctrl_items.go index 6a25663f..181ccf17 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items.go @@ -79,7 +79,7 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { ctx := services.NewContext(r.Context()) - items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GID, extractQuery(r)) + items, err := ctrl.repo.Items.QueryByGroup(ctx, ctx.GroupID, extractQuery(r)) if err != nil { if errors.Is(err, sql.ErrNoRows) { return server.JSON(w, http.StatusOK, repo.PaginationResult[repo.ItemSummary]{ @@ -105,12 +105,12 @@ func (ctrl *V1Controller) HandleItemsGetAll() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemFullPath() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) ([]repo.ItemPath, error) { auth := services.NewContext(r.Context()) - item, err := ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID) + item, err := ctrl.repo.Items.GetOneByGroup(auth, auth.GroupID, ID) if err != nil { return nil, err } - paths, err := ctrl.repo.Locations.PathForLoc(auth, auth.GID, item.Location.ID) + paths, err := ctrl.repo.Locations.PathForLoc(auth, auth.GroupID, item.Location.ID) if err != nil { return nil, err } @@ -165,7 +165,7 @@ func (ctrl *V1Controller) HandleItemGet() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (repo.ItemOut, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID) + return ctrl.repo.Items.GetOneByGroup(auth, auth.GroupID, ID) } return adapters.CommandID("id", fn, http.StatusOK) @@ -183,7 +183,7 @@ func (ctrl *V1Controller) HandleItemGet() errchain.HandlerFunc { func (ctrl *V1Controller) HandleItemDelete() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (any, error) { auth := services.NewContext(r.Context()) - err := ctrl.repo.Items.DeleteByGroup(auth, auth.GID, ID) + err := ctrl.repo.Items.DeleteByGroup(auth, auth.GroupID, ID) return nil, err } @@ -205,7 +205,7 @@ func (ctrl *V1Controller) HandleItemUpdate() errchain.HandlerFunc { auth := services.NewContext(r.Context()) body.ID = ID - return ctrl.repo.Items.UpdateByGroup(auth, auth.GID, body) + return ctrl.repo.Items.UpdateByGroup(auth, auth.GroupID, body) } return adapters.ActionID("id", fn, http.StatusOK) @@ -226,12 +226,12 @@ func (ctrl *V1Controller) HandleItemPatch() errchain.HandlerFunc { auth := services.NewContext(r.Context()) body.ID = ID - err := ctrl.repo.Items.Patch(auth, auth.GID, ID, body) + err := ctrl.repo.Items.Patch(auth, auth.GroupID, ID, body) if err != nil { return repo.ItemOut{}, err } - return ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID) + return ctrl.repo.Items.GetOneByGroup(auth, auth.GroupID, ID) } return adapters.ActionID("id", fn, http.StatusOK) @@ -249,7 +249,7 @@ func (ctrl *V1Controller) HandleItemPatch() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGetAllCustomFieldNames() errchain.HandlerFunc { fn := func(r *http.Request) ([]string, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Items.GetAllCustomFieldNames(auth, auth.GID) + return ctrl.repo.Items.GetAllCustomFieldNames(auth, auth.GroupID) } return adapters.Command(fn, http.StatusOK) @@ -271,7 +271,7 @@ func (ctrl *V1Controller) HandleGetAllCustomFieldValues() errchain.HandlerFunc { fn := func(r *http.Request, q query) ([]string, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Items.GetAllCustomFieldValues(auth, auth.GID, q.Field) + return ctrl.repo.Items.GetAllCustomFieldValues(auth, auth.GroupID, q.Field) } return adapters.Query(fn, http.StatusOK) @@ -323,7 +323,7 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { ctx := services.NewContext(r.Context()) - csvData, err := ctrl.svc.Items.ExportTSV(r.Context(), ctx.GID) + csvData, err := ctrl.svc.Items.ExportTSV(r.Context(), ctx.GroupID) if err != nil { log.Err(err).Msg("failed to export items") return validate.NewRequestError(err, http.StatusInternalServerError) 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 ae2782af..c38c175c 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items_attachments.go @@ -168,7 +168,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r // Delete Attachment Handler case http.MethodDelete: - err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GID, ID, attachmentID) + err = ctrl.svc.Items.AttachmentDelete(r.Context(), ctx.GroupID, ID, attachmentID) if err != nil { log.Err(err).Msg("failed to delete attachment") return validate.NewRequestError(err, http.StatusInternalServerError) diff --git a/backend/app/api/handlers/v1/v1_ctrl_labels.go b/backend/app/api/handlers/v1/v1_ctrl_labels.go index dae23db0..231c4bb3 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_labels.go +++ b/backend/app/api/handlers/v1/v1_ctrl_labels.go @@ -21,7 +21,7 @@ import ( func (ctrl *V1Controller) HandleLabelsGetAll() errchain.HandlerFunc { fn := func(r *http.Request) ([]repo.LabelSummary, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Labels.GetAll(auth, auth.GID) + return ctrl.repo.Labels.GetAll(auth, auth.GroupID) } return adapters.Command(fn, http.StatusOK) @@ -39,7 +39,7 @@ func (ctrl *V1Controller) HandleLabelsGetAll() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLabelsCreate() errchain.HandlerFunc { fn := func(r *http.Request, data repo.LabelCreate) (repo.LabelOut, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Labels.Create(auth, auth.GID, data) + return ctrl.repo.Labels.Create(auth, auth.GroupID, data) } return adapters.Action(fn, http.StatusCreated) @@ -57,7 +57,7 @@ func (ctrl *V1Controller) HandleLabelsCreate() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLabelDelete() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (any, error) { auth := services.NewContext(r.Context()) - err := ctrl.repo.Labels.DeleteByGroup(auth, auth.GID, ID) + err := ctrl.repo.Labels.DeleteByGroup(auth, auth.GroupID, ID) return nil, err } @@ -76,7 +76,7 @@ func (ctrl *V1Controller) HandleLabelDelete() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLabelGet() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (repo.LabelOut, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Labels.GetOneByGroup(auth, auth.GID, ID) + return ctrl.repo.Labels.GetOneByGroup(auth, auth.GroupID, ID) } return adapters.CommandID("id", fn, http.StatusOK) @@ -95,7 +95,7 @@ func (ctrl *V1Controller) HandleLabelUpdate() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID, data repo.LabelUpdate) (repo.LabelOut, error) { auth := services.NewContext(r.Context()) data.ID = ID - return ctrl.repo.Labels.UpdateByGroup(auth, auth.GID, data) + return ctrl.repo.Labels.UpdateByGroup(auth, auth.GroupID, data) } return adapters.ActionID("id", fn, http.StatusOK) diff --git a/backend/app/api/handlers/v1/v1_ctrl_locations.go b/backend/app/api/handlers/v1/v1_ctrl_locations.go index d84ce313..469035f3 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_locations.go +++ b/backend/app/api/handlers/v1/v1_ctrl_locations.go @@ -22,7 +22,7 @@ import ( func (ctrl *V1Controller) HandleLocationTreeQuery() errchain.HandlerFunc { fn := func(r *http.Request, query repo.TreeQuery) ([]repo.TreeItem, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Locations.Tree(auth, auth.GID, query) + return ctrl.repo.Locations.Tree(auth, auth.GroupID, query) } return adapters.Query(fn, http.StatusOK) @@ -40,7 +40,7 @@ func (ctrl *V1Controller) HandleLocationTreeQuery() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLocationGetAll() errchain.HandlerFunc { fn := func(r *http.Request, q repo.LocationQuery) ([]repo.LocationOutCount, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Locations.GetAll(auth, auth.GID, q) + return ctrl.repo.Locations.GetAll(auth, auth.GroupID, q) } return adapters.Query(fn, http.StatusOK) @@ -58,7 +58,7 @@ func (ctrl *V1Controller) HandleLocationGetAll() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLocationCreate() errchain.HandlerFunc { fn := func(r *http.Request, createData repo.LocationCreate) (repo.LocationOut, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Locations.Create(auth, auth.GID, createData) + return ctrl.repo.Locations.Create(auth, auth.GroupID, createData) } return adapters.Action(fn, http.StatusCreated) @@ -76,7 +76,7 @@ func (ctrl *V1Controller) HandleLocationCreate() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (any, error) { auth := services.NewContext(r.Context()) - err := ctrl.repo.Locations.DeleteByGroup(auth, auth.GID, ID) + err := ctrl.repo.Locations.DeleteByGroup(auth, auth.GroupID, ID) return nil, err } @@ -95,7 +95,7 @@ func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc { func (ctrl *V1Controller) HandleLocationGet() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (repo.LocationOut, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Locations.GetOneByGroup(auth, auth.GID, ID) + return ctrl.repo.Locations.GetOneByGroup(auth, auth.GroupID, ID) } return adapters.CommandID("id", fn, http.StatusOK) @@ -115,7 +115,7 @@ func (ctrl *V1Controller) HandleLocationUpdate() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID, body repo.LocationUpdate) (repo.LocationOut, error) { auth := services.NewContext(r.Context()) body.ID = ID - return ctrl.repo.Locations.UpdateByGroup(auth, auth.GID, ID, body) + return ctrl.repo.Locations.UpdateByGroup(auth, auth.GroupID, ID, body) } return adapters.ActionID("id", fn, http.StatusOK) diff --git a/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go b/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go index e94c12a9..d0385e79 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go +++ b/backend/app/api/handlers/v1/v1_ctrl_maint_entry.go @@ -21,7 +21,7 @@ import ( func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID, q repo.MaintenanceLogQuery) (repo.MaintenanceLog, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.MaintEntry.GetLog(auth, auth.GID, ID, q) + return ctrl.repo.MaintEntry.GetLog(auth, auth.GroupID, ID, q) } return adapters.QueryID("id", fn, http.StatusOK) diff --git a/backend/app/api/handlers/v1/v1_ctrl_notifiers.go b/backend/app/api/handlers/v1/v1_ctrl_notifiers.go index 3c64dc7f..f7ff6f42 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_notifiers.go +++ b/backend/app/api/handlers/v1/v1_ctrl_notifiers.go @@ -40,7 +40,7 @@ func (ctrl *V1Controller) HandleGetUserNotifiers() errchain.HandlerFunc { func (ctrl *V1Controller) HandleCreateNotifier() errchain.HandlerFunc { fn := func(r *http.Request, in repo.NotifierCreate) (repo.NotifierOut, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Notifiers.Create(auth, auth.GID, auth.UID, in) + return ctrl.repo.Notifiers.Create(auth, auth.GroupID, auth.UserID, in) } return adapters.Action(fn, http.StatusCreated) @@ -57,7 +57,7 @@ func (ctrl *V1Controller) HandleCreateNotifier() errchain.HandlerFunc { func (ctrl *V1Controller) HandleDeleteNotifier() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID) (any, error) { auth := services.NewContext(r.Context()) - return nil, ctrl.repo.Notifiers.Delete(auth, auth.UID, ID) + return nil, ctrl.repo.Notifiers.Delete(auth, auth.UserID, ID) } return adapters.CommandID("id", fn, http.StatusNoContent) @@ -75,7 +75,7 @@ func (ctrl *V1Controller) HandleDeleteNotifier() errchain.HandlerFunc { func (ctrl *V1Controller) HandleUpdateNotifier() errchain.HandlerFunc { fn := func(r *http.Request, ID uuid.UUID, in repo.NotifierUpdate) (repo.NotifierOut, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Notifiers.Update(auth, auth.UID, ID, in) + return ctrl.repo.Notifiers.Update(auth, auth.UserID, ID, in) } return adapters.ActionID("id", fn, http.StatusOK) diff --git a/backend/app/api/handlers/v1/v1_ctrl_statistics.go b/backend/app/api/handlers/v1/v1_ctrl_statistics.go index 0a5a319c..b49a7a18 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_statistics.go +++ b/backend/app/api/handlers/v1/v1_ctrl_statistics.go @@ -23,7 +23,7 @@ import ( func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc { fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Groups.StatsLocationsByPurchasePrice(auth, auth.GID) + return ctrl.repo.Groups.StatsLocationsByPurchasePrice(auth, auth.GroupID) } return adapters.Command(fn, http.StatusOK) @@ -40,7 +40,7 @@ func (ctrl *V1Controller) HandleGroupStatisticsLocations() errchain.HandlerFunc func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc { fn := func(r *http.Request) ([]repo.TotalsByOrganizer, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GID) + return ctrl.repo.Groups.StatsLabelsByPurchasePrice(auth, auth.GroupID) } return adapters.Command(fn, http.StatusOK) @@ -57,7 +57,7 @@ func (ctrl *V1Controller) HandleGroupStatisticsLabels() errchain.HandlerFunc { func (ctrl *V1Controller) HandleGroupStatistics() errchain.HandlerFunc { fn := func(r *http.Request) (repo.GroupStatistics, error) { auth := services.NewContext(r.Context()) - return ctrl.repo.Groups.StatsGroup(auth, auth.GID) + return ctrl.repo.Groups.StatsGroup(auth, auth.GroupID) } return adapters.Command(fn, http.StatusOK) @@ -94,7 +94,7 @@ func (ctrl *V1Controller) HandleGroupStatisticsPriceOverTime() errchain.HandlerF return validate.NewRequestError(err, http.StatusBadRequest) } - stats, err := ctrl.repo.Groups.StatsPurchasePrice(ctx, ctx.GID, startDate, endDate) + stats, err := ctrl.repo.Groups.StatsPurchasePrice(ctx, ctx.GroupID, startDate, endDate) if err != nil { return validate.NewRequestError(err, http.StatusInternalServerError) } diff --git a/backend/app/api/handlers/v1/v1_ctrl_user.go b/backend/app/api/handlers/v1/v1_ctrl_user.go index 8708d24c..8a05088d 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_user.go +++ b/backend/app/api/handlers/v1/v1_ctrl_user.go @@ -1,6 +1,7 @@ package v1 import ( + "context" "fmt" "net/http" @@ -8,6 +9,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/core/services" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/sys/validate" + "github.com/hay-kot/homebox/backend/internal/web/adapters" "github.com/hay-kot/httpkit/errchain" "github.com/hay-kot/httpkit/server" "github.com/rs/zerolog/log" @@ -115,12 +117,10 @@ func (ctrl *V1Controller) HandleUserSelfDelete() errchain.HandlerFunc { } } -type ( - ChangePassword struct { - Current string `json:"current,omitempty"` - New string `json:"new,omitempty"` - } -) +type ChangePassword struct { + Current string `json:"current,omitempty"` + New string `json:"new,omitempty"` +} // HandleUserSelfChangePassword godoc // @@ -144,7 +144,7 @@ func (ctrl *V1Controller) HandleUserSelfChangePassword() errchain.HandlerFunc { ctx := services.NewContext(r.Context()) - ok := ctrl.svc.User.ChangePassword(ctx, cp.Current, cp.New) + ok := ctrl.svc.User.PasswordChange(ctx, cp.Current, cp.New) if !ok { return validate.NewRequestError(err, http.StatusInternalServerError) } @@ -152,3 +152,73 @@ func (ctrl *V1Controller) HandleUserSelfChangePassword() errchain.HandlerFunc { return server.JSON(w, http.StatusNoContent, nil) } } + +// HandleUserSelfChangePasswordWithToken godoc +// +// @Summary Change Password +// @Tags User +// @Success 204 +// @Param payload body ChangePassword true "Password Payload" +// @Router /v1/users/change-password-token [PUT] +func (ctrl *V1Controller) HandleUserSelfChangePasswordWithToken() errchain.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) error { + tokenQueryParam := r.URL.Query().Get("token") + if tokenQueryParam == "" { + return validate.NewRequestError(fmt.Errorf("missing token query param"), http.StatusBadRequest) + } + + if ctrl.isDemo { + return validate.NewRequestError(nil, http.StatusForbidden) + } + + var cp ChangePassword + err := server.Decode(r, &cp) + if err != nil { + log.Err(err).Msg("user failed to change password") + } + + ctx := services.NewContext(r.Context()) + + ok := ctrl.svc.User.PasswordChange(ctx, cp.Current, cp.New) + if !ok { + return validate.NewRequestError(err, http.StatusInternalServerError) + } + + return server.JSON(w, http.StatusNoContent, nil) + } +} + +// HandleUserRequestPasswordReset godoc +// +// @Summary Request Password Reset +// @Tags User +// @Produce json +// @Param payload body services.PasswordResetRequest true "User Data" +// @Success 204 +// @Router /v1/users/request-password-reset [Post] +func (ctrl *V1Controller) HandleUserRequestPasswordReset() errchain.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) error { + if ctrl.isDemo { + return validate.NewRequestError(nil, http.StatusForbidden) + } + + v, err := adapters.DecodeBody[services.PasswordResetRequest](r) + if err != nil { + return err + } + + go func() { + ctx := context.Background() + + err = ctrl.svc.User.PasswordResetRequest(ctx, v) + if err != nil { + log.Warn(). + Err(err). + Str("email", v.Email). + Msg("failed to request password reset") + } + }() + + return server.JSON(w, http.StatusNoContent, nil) + } +} diff --git a/backend/app/api/main.go b/backend/app/api/main.go index 4811bfa0..e90c225e 100644 --- a/backend/app/api/main.go +++ b/backend/app/api/main.go @@ -115,11 +115,11 @@ func run(cfg *config.Config) error { err = c.Schema.Create(context.Background(), options...) if err != nil { - log.Fatal(). - Err(err). - Str("driver", "sqlite"). - Str("url", cfg.Storage.SqliteURL). - Msg("failed creating schema resources") + log.Fatal(). // nolint + Err(err). + Str("driver", "sqlite"). + Str("url", cfg.Storage.SqliteURL). + Msg("failed creating schema resources") } err = os.RemoveAll(temp) @@ -160,6 +160,8 @@ func run(cfg *config.Config) error { app.repos = repo.New(c, app.bus, cfg.Storage.Data) app.services = services.New( app.repos, + app.conf.BaseURL, + app.mailer, services.WithAutoIncrementAssetID(cfg.Options.AutoIncrementAssetID), services.WithCurrencies(currencies), ) @@ -180,7 +182,7 @@ func run(cfg *config.Config) error { chain := errchain.New(mid.Errors(logger)) - app.mountRoutes(router, chain, app.repos) + app.mountRoutes(router, chain) runner := graceful.NewRunner() diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index de10942e..af975692 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -15,7 +15,6 @@ import ( "github.com/hay-kot/homebox/backend/app/api/providers" _ "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/httpkit/errchain" httpSwagger "github.com/swaggo/http-swagger/v2" // http-swagger middleware ) @@ -37,7 +36,7 @@ func (a *app) debugRouter() *http.ServeMux { } // registerRoutes registers all the routes for the API -func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllRepos) { +func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain) { registerMimes() r.Get("/swagger/*", httpSwagger.Handler( @@ -72,6 +71,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR r.Post(v1Base("/users/register"), chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration())) r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...))) + r.Post(v1Base("/users/request-password-reset"), chain.ToHandlerFunc(v1Ctrl.HandleUserRequestPasswordReset())) userMW := []errchain.Middleware{ a.mwAuthToken, @@ -85,6 +85,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR r.Post(v1Base("/users/logout"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...)) r.Get(v1Base("/users/refresh"), chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...)) r.Put(v1Base("/users/self/change-password"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...)) + r.Put(v1Base("/users/self/change-password-token"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePasswordWithToken())) r.Post(v1Base("/groups/invitations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...)) r.Get(v1Base("/groups/statistics"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...)) diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index 7c9a7486..ec99f22f 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -1792,6 +1792,33 @@ const docTemplate = `{ } } }, + "/v1/users/request-password-reset": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Request Password Reset", + "parameters": [ + { + "description": "User Data", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/services.PasswordResetRequest" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, "/v1/users/self": { "get": { "security": [ @@ -2825,6 +2852,14 @@ const docTemplate = `{ } } }, + "services.PasswordResetRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + } + } + }, "services.UserRegistration": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index b10c93ad..e87d3b36 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -1785,6 +1785,33 @@ } } }, + "/v1/users/request-password-reset": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Request Password Reset", + "parameters": [ + { + "description": "User Data", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/services.PasswordResetRequest" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, "/v1/users/self": { "get": { "security": [ @@ -2818,6 +2845,14 @@ } } }, + "services.PasswordResetRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + } + } + }, "services.UserRegistration": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index dbb31e6c..386cb183 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -620,6 +620,11 @@ definitions: value: type: number type: object + services.PasswordResetRequest: + properties: + email: + type: string + type: object services.UserRegistration: properties: email: @@ -1817,6 +1822,23 @@ paths: summary: Register New User tags: - User + /v1/users/request-password-reset: + post: + parameters: + - description: User Data + in: body + name: payload + required: true + schema: + $ref: '#/definitions/services.PasswordResetRequest' + produces: + - application/json + responses: + "204": + description: No Content + summary: Request Password Reset + tags: + - User /v1/users/self: delete: produces: diff --git a/backend/app/tools/typegen/main.go b/backend/app/tools/typegen/main.go index 5f4d8dad..d6e1798f 100644 --- a/backend/app/tools/typegen/main.go +++ b/backend/app/tools/typegen/main.go @@ -71,7 +71,7 @@ func main() { text = replace.Regex.ReplaceAllString(text, replace.Text) } - err = os.WriteFile(path, []byte(text), 0644) + err = os.WriteFile(path, []byte(text), 0o644) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/backend/go.mod b/backend/go.mod index 8a0f9d95..9ddcc696 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -14,6 +14,7 @@ require ( github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a github.com/google/uuid v1.6.0 github.com/gorilla/schema v1.2.1 + github.com/hay-kot/easyemails v0.0.0-20240206011027-25232fb79aa3 github.com/hay-kot/httpkit v0.0.9 github.com/mattn/go-sqlite3 v1.14.22 github.com/olahol/melody v1.1.4 diff --git a/backend/go.sum b/backend/go.sum index fec97064..0766626a 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,5 +1,3 @@ -ariga.io/atlas v0.19.0 h1:gilVpXabeiGhGI9lj/rQURkXBemnloc41RGOtwVLNc4= -ariga.io/atlas v0.19.0/go.mod h1:uj3pm+hUTVN/X5yfdBexHlZv+1Xu5u5ZbZx7+CDavNU= ariga.io/atlas v0.19.1 h1:QzBHkakwzEhmPWOzNhw8Yr/Bbicj6Iq5hwEoNI/Jr9A= ariga.io/atlas v0.19.1/go.mod h1:VPlcXdd4w2KqKnH54yEZcry79UAhpaWaxEsmn5JRNoE= entgo.io/ent v0.12.5 h1:KREM5E4CSoej4zeGa88Ou/gfturAnpUv0mzAjch1sj4= @@ -14,6 +12,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/ardanlabs/conf/v3 v3.1.7 h1:p232cF68TafoA5U9ZlbxUIhGJtGNdKHBXF80Fdqb5t0= github.com/ardanlabs/conf/v3 v3.1.7/go.mod h1:zclexWKe0NVj6LHQ8NgDDZ7bQ1spE0KeKPFficdtAjU= +github.com/bradleyjkemp/cupaloy v2.3.0+incompatible h1:UafIjBvWQmS9i/xRg+CamMrnLTKNzo+bdmT/oH34c2Y= +github.com/bradleyjkemp/cupaloy v2.3.0+incompatible/go.mod h1:Au1Xw1sgaJ5iSFktEhYsS0dbQiS1B0/XMXl+42y9Ilk= github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec= github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -84,12 +84,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= -github.com/hay-kot/httpkit v0.0.6 h1:BidC4UrkS7zRhoTdpKLeF8ODJPKcOZkJ2tk2t2ZIQjQ= -github.com/hay-kot/httpkit v0.0.6/go.mod h1:1s/OJwWRyH6tBtTw76jTp6kwBYvjswziXaokPQH7eKQ= -github.com/hay-kot/httpkit v0.0.7 h1:KxGi+MwXFavfFUfJEMpye5cnMef9TlFu3v7UZipUB8U= -github.com/hay-kot/httpkit v0.0.7/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU= -github.com/hay-kot/httpkit v0.0.8 h1:n+Z5z35YZcdD9cGwbnIPRbrgDw9LY6lqakH4zYr5z+A= -github.com/hay-kot/httpkit v0.0.8/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU= +github.com/hay-kot/easyemails v0.0.0-20240206011027-25232fb79aa3 h1:EljujAOvukSS+sh8e883j4vhEiYG4EvJFAhl+XvUYe8= +github.com/hay-kot/easyemails v0.0.0-20240206011027-25232fb79aa3/go.mod h1:SZdhYMO2ZmEuTTDE7pHn0WanXhwteniOCYsQ3B5IT/o= github.com/hay-kot/httpkit v0.0.9 h1:hu2TPY9awmIYWXxWGubaXl2U61pPvaVsm9YwboBRGu0= github.com/hay-kot/httpkit v0.0.9/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= @@ -173,8 +169,6 @@ golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= diff --git a/backend/internal/core/services/all.go b/backend/internal/core/services/all.go index 3c03a4e9..b106c561 100644 --- a/backend/internal/core/services/all.go +++ b/backend/internal/core/services/all.go @@ -4,6 +4,7 @@ package services import ( "github.com/hay-kot/homebox/backend/internal/core/currencies" "github.com/hay-kot/homebox/backend/internal/data/repo" + "github.com/hay-kot/homebox/backend/pkgs/mailer" ) type AllServices struct { @@ -33,7 +34,7 @@ func WithCurrencies(v []currencies.Currency) func(*options) { } } -func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices { +func New(repos *repo.AllRepos, baseurl string, sender *mailer.Mailer, opts ...OptionsFunc) *AllServices { if repos == nil { panic("repos cannot be nil") } @@ -55,7 +56,11 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices { } return &AllServices{ - User: &UserService{repos}, + User: &UserService{ + repos: repos, + mailer: sender, + baseurl: baseurl, + }, Group: &GroupService{repos}, Items: &ItemService{ repo: repos, diff --git a/backend/internal/core/services/contexts.go b/backend/internal/core/services/contexts.go index d82dcfa7..9d03eea6 100644 --- a/backend/internal/core/services/contexts.go +++ b/backend/internal/core/services/contexts.go @@ -19,11 +19,11 @@ var ( type Context struct { context.Context - // UID is a unique identifier for the acting user. - UID uuid.UUID + // UserID is a unique identifier for the acting user. + UserID uuid.UUID - // GID is a unique identifier for the acting users group. - GID uuid.UUID + // GroupID is a unique identifier for the acting users group. + GroupID uuid.UUID // User is the acting user. User *repo.UserOut @@ -35,8 +35,8 @@ func NewContext(ctx context.Context) Context { user := UseUserCtx(ctx) return Context{ Context: ctx, - UID: user.ID, - GID: user.GroupID, + UserID: user.ID, + GroupID: user.GroupID, User: user, } } diff --git a/backend/internal/core/services/main_test.go b/backend/internal/core/services/main_test.go index ecb07b0b..05dd11e4 100644 --- a/backend/internal/core/services/main_test.go +++ b/backend/internal/core/services/main_test.go @@ -11,6 +11,7 @@ import ( "github.com/hay-kot/homebox/backend/internal/data/ent" "github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/pkgs/faker" + "github.com/hay-kot/homebox/backend/pkgs/mailer" _ "github.com/mattn/go-sqlite3" ) @@ -67,15 +68,16 @@ func TestMain(m *testing.M) { currencies.CollectDefaults(), ) - tSvc = New(tRepos, WithCurrencies(defaults)) - defer func() { _ = client.Close() }() + tSvc = New(tRepos, "", &mailer.Mailer{}, WithCurrencies(defaults)) bootstrap() tCtx = Context{ Context: context.Background(), - GID: tGroup.ID, - UID: tUser.ID, + GroupID: tGroup.ID, + UserID: tUser.ID, } - os.Exit(m.Run()) + exit := m.Run() + _ = client.Close() + os.Exit(exit) } diff --git a/backend/internal/core/services/reporting/io_sheet.go b/backend/internal/core/services/reporting/io_sheet.go index 5877f3e8..f14ae703 100644 --- a/backend/internal/core/services/reporting/io_sheet.go +++ b/backend/internal/core/services/reporting/io_sheet.go @@ -153,7 +153,7 @@ func (s *IOSheet) Read(data io.Reader) error { } // ReadItems writes the sheet to a writer. -func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.UUID, repos *repo.AllRepos) error { +func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, groupID uuid.UUID, repos *repo.AllRepos) error { s.Rows = make([]ExportTSVRow, len(items)) extraHeaders := map[string]struct{}{} @@ -164,7 +164,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid. // TODO: Support fetching nested locations locID := item.Location.ID - locPaths, err := repos.Locations.PathForLoc(context.Background(), GID, locID) + locPaths, err := repos.Locations.PathForLoc(context.Background(), groupID, locID) if err != nil { log.Error().Err(err).Msg("could not get location path") return err diff --git a/backend/internal/core/services/service_background.go b/backend/internal/core/services/service_background.go index 21ae4c36..0102550e 100644 --- a/backend/internal/core/services/service_background.go +++ b/backend/internal/core/services/service_background.go @@ -66,7 +66,6 @@ func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error { var sendErrs []error for i := range urls { err := shoutrrr.Send(urls[i], bldr.String()) - if err != nil { sendErrs = append(sendErrs, err) } diff --git a/backend/internal/core/services/service_group.go b/backend/internal/core/services/service_group.go index 2ca3f86f..c1fccc6a 100644 --- a/backend/internal/core/services/service_group.go +++ b/backend/internal/core/services/service_group.go @@ -21,13 +21,13 @@ func (svc *GroupService) UpdateGroup(ctx Context, data repo.GroupUpdate) (repo.G return repo.Group{}, errors.New("currency cannot be empty") } - return svc.repos.Groups.GroupUpdate(ctx.Context, ctx.GID, data) + return svc.repos.Groups.GroupUpdate(ctx.Context, ctx.GroupID, data) } func (svc *GroupService) NewInvitation(ctx Context, uses int, expiresAt time.Time) (string, error) { token := hasher.GenerateToken() - _, err := svc.repos.Groups.InvitationCreate(ctx, ctx.GID, repo.GroupInvitationCreate{ + _, err := svc.repos.Groups.InvitationCreate(ctx, ctx.GroupID, repo.GroupInvitationCreate{ Token: token.Hash, Uses: uses, ExpiresAt: expiresAt, diff --git a/backend/internal/core/services/service_items.go b/backend/internal/core/services/service_items.go index 4d510e5c..547da26b 100644 --- a/backend/internal/core/services/service_items.go +++ b/backend/internal/core/services/service_items.go @@ -27,7 +27,7 @@ type ItemService struct { func (svc *ItemService) Create(ctx Context, item repo.ItemCreate) (repo.ItemOut, error) { if svc.autoIncrementAssetID { - highest, err := svc.repo.Items.GetHighestAssetID(ctx, ctx.GID) + highest, err := svc.repo.Items.GetHighestAssetID(ctx, ctx.GroupID) if err != nil { return repo.ItemOut{}, err } @@ -35,16 +35,16 @@ func (svc *ItemService) Create(ctx Context, item repo.ItemCreate) (repo.ItemOut, item.AssetID = highest + 1 } - return svc.repo.Items.Create(ctx, ctx.GID, item) + return svc.repo.Items.Create(ctx, ctx.GroupID, item) } -func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int, error) { - items, err := svc.repo.Items.GetAllZeroAssetID(ctx, GID) +func (svc *ItemService) EnsureAssetID(ctx context.Context, groupID uuid.UUID) (int, error) { + items, err := svc.repo.Items.GetAllZeroAssetID(ctx, groupID) if err != nil { return 0, err } - highest, err := svc.repo.Items.GetHighestAssetID(ctx, GID) + highest, err := svc.repo.Items.GetHighestAssetID(ctx, groupID) if err != nil { return 0, err } @@ -53,7 +53,7 @@ func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int, for _, item := range items { highest++ - err = svc.repo.Items.SetAssetID(ctx, GID, item.ID, highest) + err = svc.repo.Items.SetAssetID(ctx, groupID, item.ID, highest) if err != nil { return 0, err } @@ -64,8 +64,8 @@ func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int, return finished, nil } -func (svc *ItemService) EnsureImportRef(ctx context.Context, GID uuid.UUID) (int, error) { - ids, err := svc.repo.Items.GetAllZeroImportRef(ctx, GID) +func (svc *ItemService) EnsureImportRef(ctx context.Context, groupID uuid.UUID) (int, error) { + ids, err := svc.repo.Items.GetAllZeroImportRef(ctx, groupID) if err != nil { return 0, err } @@ -74,7 +74,7 @@ func (svc *ItemService) EnsureImportRef(ctx context.Context, GID uuid.UUID) (int for _, itemID := range ids { ref := uuid.New().String()[0:8] - err = svc.repo.Items.Patch(ctx, GID, itemID, repo.ItemPatch{ImportRef: &ref}) + err = svc.repo.Items.Patch(ctx, groupID, itemID, repo.ItemPatch{ImportRef: &ref}) if err != nil { return 0, err } @@ -96,7 +96,7 @@ func serializeLocation[T ~[]string](location T) string { // 1. If the item does not exist, it is created. // 2. If the item has a ImportRef and it exists it is skipped // 3. Locations and Labels are created if they do not exist. -func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Reader) (int, error) { +func (svc *ItemService) CsvImport(ctx context.Context, groupID uuid.UUID, data io.Reader) (int, error) { sheet := reporting.IOSheet{} err := sheet.Read(data) @@ -109,7 +109,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re labelMap := make(map[string]uuid.UUID) { - labels, err := svc.repo.Labels.GetAll(ctx, GID) + labels, err := svc.repo.Labels.GetAll(ctx, groupID) if err != nil { return 0, err } @@ -124,7 +124,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re locationMap := make(map[string]uuid.UUID) { - locations, err := svc.repo.Locations.Tree(ctx, GID, repo.TreeQuery{WithItems: false}) + locations, err := svc.repo.Locations.Tree(ctx, groupID, repo.TreeQuery{WithItems: false}) if err != nil { return 0, err } @@ -153,7 +153,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re // Asset ID Pre-Check highestAID := repo.AssetID(-1) if svc.autoIncrementAssetID { - highestAID, err = svc.repo.Items.GetHighestAssetID(ctx, GID) + highestAID, err = svc.repo.Items.GetHighestAssetID(ctx, groupID) if err != nil { return 0, err } @@ -169,7 +169,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re // ======================================== // Preflight check for existing item if row.ImportRef != "" { - exists, err := svc.repo.Items.CheckRef(ctx, GID, row.ImportRef) + exists, err := svc.repo.Items.CheckRef(ctx, groupID, row.ImportRef) if err != nil { return 0, fmt.Errorf("error checking for existing item with ref %q: %w", row.ImportRef, err) } @@ -188,7 +188,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re id, ok := labelMap[label] if !ok { - newLabel, err := svc.repo.Labels.Create(ctx, GID, repo.LabelCreate{Name: label}) + newLabel, err := svc.repo.Labels.Create(ctx, groupID, repo.LabelCreate{Name: label}) if err != nil { return 0, err } @@ -220,7 +220,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re parentID = locationMap[parentPath] } - newLocation, err := svc.repo.Locations.Create(ctx, GID, repo.LocationCreate{ + newLocation, err := svc.repo.Locations.Create(ctx, groupID, repo.LocationCreate{ ParentID: parentID, Name: pathElement, }) @@ -261,12 +261,12 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re LabelIDs: labelIds, } - item, err = svc.repo.Items.Create(ctx, GID, newItem) + item, err = svc.repo.Items.Create(ctx, groupID, newItem) if err != nil { return 0, err } default: - item, err = svc.repo.Items.GetByRef(ctx, GID, row.ImportRef) + item, err = svc.repo.Items.GetByRef(ctx, groupID, row.ImportRef) if err != nil { return 0, err } @@ -318,7 +318,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re Fields: fields, } - item, err = svc.repo.Items.UpdateByGroup(ctx, GID, updateItem) + item, err = svc.repo.Items.UpdateByGroup(ctx, groupID, updateItem) if err != nil { return 0, err } @@ -329,15 +329,15 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re return finished, nil } -func (svc *ItemService) ExportTSV(ctx context.Context, GID uuid.UUID) ([][]string, error) { - items, err := svc.repo.Items.GetAll(ctx, GID) +func (svc *ItemService) ExportTSV(ctx context.Context, groupID uuid.UUID) ([][]string, error) { + items, err := svc.repo.Items.GetAll(ctx, groupID) if err != nil { return nil, err } sheet := reporting.IOSheet{} - err = sheet.ReadItems(ctx, items, GID, svc.repo) + err = sheet.ReadItems(ctx, items, groupID, svc.repo) if err != nil { return nil, err } @@ -345,8 +345,8 @@ func (svc *ItemService) ExportTSV(ctx context.Context, GID uuid.UUID) ([][]strin return sheet.TSV() } -func (svc *ItemService) ExportBillOfMaterialsTSV(ctx context.Context, GID uuid.UUID) ([]byte, error) { - items, err := svc.repo.Items.GetAll(ctx, GID) +func (svc *ItemService) ExportBillOfMaterialsTSV(ctx context.Context, groupID uuid.UUID) ([]byte, error) { + items, err := svc.repo.Items.GetAll(ctx, groupID) if err != nil { return nil, err } diff --git a/backend/internal/core/services/service_items_attachments.go b/backend/internal/core/services/service_items_attachments.go index 43835c63..341b4baa 100644 --- a/backend/internal/core/services/service_items_attachments.go +++ b/backend/internal/core/services/service_items_attachments.go @@ -35,7 +35,7 @@ func (svc *ItemService) AttachmentUpdate(ctx Context, itemID uuid.UUID, data *re return repo.ItemOut{}, err } - return svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemID) + return svc.repo.Items.GetOneByGroup(ctx, ctx.GroupID, itemID) } // AttachmentAdd adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment @@ -43,13 +43,13 @@ func (svc *ItemService) AttachmentUpdate(ctx Context, itemID uuid.UUID, data *re // relative path during construction of the service. func (svc *ItemService) AttachmentAdd(ctx Context, itemID uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (repo.ItemOut, error) { // Get the Item - _, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemID) + _, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GroupID, itemID) if err != nil { return repo.ItemOut{}, err } // Create the document - doc, err := svc.repo.Docs.Create(ctx, ctx.GID, repo.DocumentCreate{Title: filename, Content: file}) + doc, err := svc.repo.Docs.Create(ctx, ctx.GroupID, repo.DocumentCreate{Title: filename, Content: file}) if err != nil { log.Err(err).Msg("failed to create document") return repo.ItemOut{}, err @@ -62,7 +62,7 @@ func (svc *ItemService) AttachmentAdd(ctx Context, itemID uuid.UUID, filename st return repo.ItemOut{}, err } - return svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemID) + return svc.repo.Items.GetOneByGroup(ctx, ctx.GroupID, itemID) } func (svc *ItemService) AttachmentDelete(ctx context.Context, gid, itemID, attachmentID uuid.UUID) error { diff --git a/backend/internal/core/services/service_user.go b/backend/internal/core/services/service_user.go index d86c39b4..05c5a6f0 100644 --- a/backend/internal/core/services/service_user.go +++ b/backend/internal/core/services/service_user.go @@ -3,12 +3,15 @@ package services import ( "context" "errors" + "net/url" "time" "github.com/google/uuid" + "github.com/hay-kot/easyemails" "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/hay-kot/homebox/backend/pkgs/mailer" "github.com/rs/zerolog/log" ) @@ -19,8 +22,15 @@ var ( ErrorTokenIDMismatch = errors.New("token id mismatch") ) +func init() { // nolint: gochecknoinits + easyemails.ImageLogoHeader = "https://raw.githubusercontent.com/hay-kot/homebox/af9aa239af66df17478f5ed9283e303daf7c6775/docs/docs/assets/img/homebox-email-banner.jpg" + easyemails.ColorPrimary = "#5D7F67" +} + type UserService struct { - repos *repo.AllRepos + repos *repo.AllRepos + mailer *mailer.Mailer + baseurl string } type ( @@ -39,6 +49,9 @@ type ( Username string `json:"username"` Password string `json:"password"` } + PasswordResetRequest struct { + Email string `json:"email"` + } ) // RegisterUser creates a new user and group in the data with the provided data. It also bootstraps the user's group @@ -132,13 +145,13 @@ func (svc *UserService) GetSelf(ctx context.Context, requestToken string) (repo. return svc.repos.AuthTokens.GetUserFromToken(ctx, hash) } -func (svc *UserService) UpdateSelf(ctx context.Context, ID uuid.UUID, data repo.UserUpdate) (repo.UserOut, error) { - err := svc.repos.Users.Update(ctx, ID, data) +func (svc *UserService) UpdateSelf(ctx context.Context, userID uuid.UUID, data repo.UserUpdate) (repo.UserOut, error) { + err := svc.repos.Users.Update(ctx, userID, data) if err != nil { return repo.UserOut{}, err } - return svc.repos.Users.GetOneID(ctx, ID) + return svc.repos.Users.GetOneID(ctx, userID) } // ============================================================================ @@ -217,28 +230,28 @@ func (svc *UserService) RenewToken(ctx context.Context, token string) (UserAuthT // DeleteSelf deletes the user that is currently logged based of the provided UUID // There is _NO_ protection against deleting the wrong user, as such this should only // be used when the identify of the user has been confirmed. -func (svc *UserService) DeleteSelf(ctx context.Context, ID uuid.UUID) error { - return svc.repos.Users.Delete(ctx, ID) +func (svc *UserService) DeleteSelf(ctx context.Context, userID uuid.UUID) error { + return svc.repos.Users.Delete(ctx, userID) } -func (svc *UserService) ChangePassword(ctx Context, current string, new string) (ok bool) { - usr, err := svc.repos.Users.GetOneID(ctx, ctx.UID) +func (svc *UserService) PasswordChange(ctx Context, currentPassword, newPassword string) (ok bool) { + usr, err := svc.repos.Users.GetOneID(ctx, ctx.UserID) if err != nil { return false } - if !hasher.CheckPasswordHash(current, usr.PasswordHash) { + if !hasher.CheckPasswordHash(currentPassword, usr.PasswordHash) { log.Err(errors.New("current password is incorrect")).Msg("Failed to change password") return false } - hashed, err := hasher.HashPassword(new) + hashed, err := hasher.HashPassword(newPassword) if err != nil { log.Err(err).Msg("Failed to hash password") return false } - err = svc.repos.Users.ChangePassword(ctx.Context, ctx.UID, hashed) + err = svc.repos.Users.ChangePassword(ctx.Context, ctx.UserID, hashed) if err != nil { log.Err(err).Msg("Failed to change password") return false @@ -246,3 +259,80 @@ func (svc *UserService) ChangePassword(ctx Context, current string, new string) return true } + +func (svc *UserService) PasswordChangeWithToken(ctx Context, token, newPassword string) error { + hashed, err := hasher.HashPassword(newPassword) + if err != nil { + return err + } + + tokenHash := hasher.HashToken(token) + + resetToken, err := svc.repos.Users.PasswordResetGet(ctx.Context, tokenHash) + if err != nil { + return err + } + + if resetToken.UserID != ctx.UserID { + return ErrorTokenIDMismatch + } + + err = svc.repos.Users.ChangePassword(ctx.Context, ctx.UserID, hashed) + if err != nil { + return err + } + + err = svc.repos.Users.PasswordResetDelete(ctx.Context, tokenHash) + if err != nil { + return err + } + + return nil +} + +func (svc *UserService) PasswordResetRequest(ctx context.Context, req PasswordResetRequest) error { + usr, err := svc.repos.Users.GetOneEmail(ctx, req.Email) + if err != nil { + log.Warn().Err(err).Msg("failed to get user for email reset") + return err + } + + token := hasher.GenerateToken() + err = svc.repos.Users.PasswordResetCreate(ctx, usr.ID, token.Hash) + if err != nil { + return err + } + + resetURL, err := url.JoinPath(svc.baseurl, "reset-password/") + if err != nil { + return err + } + + resetURL = resetURL + "?token=" + token.Raw + + bldr := easyemails.NewBuilder().Add( + easyemails.WithParagraph( + easyemails.WithText("You have requested a password reset. Please click the link below to reset your password."), + ), + easyemails.WithButton("Reset Password", resetURL), + easyemails.WithParagraph( + easyemails.WithText("[Github](https://github.com/hay-kot/homebox) ยท [Docs](https://hay-kot.github.io/homebox/)"). + Centered(), + ). + FontSize(12), + ) + + msg := mailer.NewMessageBuilder(). + SetBody(bldr.Render()). + SetSubject("Password Reset"). + SetTo(usr.Name, usr.Email). + Build() + + err = svc.mailer.Send(msg) + if err != nil { + log.Err(err).Msg("Failed to send password reset email") + return err + } + + return nil +} diff --git a/backend/internal/data/ent/actiontoken.go b/backend/internal/data/ent/actiontoken.go new file mode 100644 index 00000000..4327df5d --- /dev/null +++ b/backend/internal/data/ent/actiontoken.go @@ -0,0 +1,184 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken" + "github.com/hay-kot/homebox/backend/internal/data/ent/user" +) + +// ActionToken is the model entity for the ActionToken schema. +type ActionToken struct { + config `json:"-"` + // ID of the ent. + ID uuid.UUID `json:"id,omitempty"` + // UserID holds the value of the "user_id" field. + UserID uuid.UUID `json:"user_id,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // Action holds the value of the "action" field. + Action actiontoken.Action `json:"action,omitempty"` + // Token holds the value of the "token" field. + Token []byte `json:"token,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the ActionTokenQuery when eager-loading is set. + Edges ActionTokenEdges `json:"edges"` + selectValues sql.SelectValues +} + +// ActionTokenEdges holds the relations/edges for other nodes in the graph. +type ActionTokenEdges struct { + // User holds the value of the user edge. + User *User `json:"user,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// UserOrErr returns the User value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e ActionTokenEdges) UserOrErr() (*User, error) { + if e.loadedTypes[0] { + if e.User == nil { + // Edge was loaded but was not found. + return nil, &NotFoundError{label: user.Label} + } + return e.User, nil + } + return nil, &NotLoadedError{edge: "user"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*ActionToken) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case actiontoken.FieldToken: + values[i] = new([]byte) + case actiontoken.FieldAction: + values[i] = new(sql.NullString) + case actiontoken.FieldCreatedAt, actiontoken.FieldUpdatedAt: + values[i] = new(sql.NullTime) + case actiontoken.FieldID, actiontoken.FieldUserID: + values[i] = new(uuid.UUID) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the ActionToken fields. +func (at *ActionToken) 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 actiontoken.FieldID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value != nil { + at.ID = *value + } + case actiontoken.FieldUserID: + if value, ok := values[i].(*uuid.UUID); !ok { + return fmt.Errorf("unexpected type %T for field user_id", values[i]) + } else if value != nil { + at.UserID = *value + } + case actiontoken.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + at.CreatedAt = value.Time + } + case actiontoken.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + at.UpdatedAt = value.Time + } + case actiontoken.FieldAction: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field action", values[i]) + } else if value.Valid { + at.Action = actiontoken.Action(value.String) + } + case actiontoken.FieldToken: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field token", values[i]) + } else if value != nil { + at.Token = *value + } + default: + at.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the ActionToken. +// This includes values selected through modifiers, order, etc. +func (at *ActionToken) Value(name string) (ent.Value, error) { + return at.selectValues.Get(name) +} + +// QueryUser queries the "user" edge of the ActionToken entity. +func (at *ActionToken) QueryUser() *UserQuery { + return NewActionTokenClient(at.config).QueryUser(at) +} + +// Update returns a builder for updating this ActionToken. +// Note that you need to call ActionToken.Unwrap() before calling this method if this ActionToken +// was returned from a transaction, and the transaction was committed or rolled back. +func (at *ActionToken) Update() *ActionTokenUpdateOne { + return NewActionTokenClient(at.config).UpdateOne(at) +} + +// Unwrap unwraps the ActionToken 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 (at *ActionToken) Unwrap() *ActionToken { + _tx, ok := at.config.driver.(*txDriver) + if !ok { + panic("ent: ActionToken is not a transactional entity") + } + at.config.driver = _tx.drv + return at +} + +// String implements the fmt.Stringer. +func (at *ActionToken) String() string { + var builder strings.Builder + builder.WriteString("ActionToken(") + builder.WriteString(fmt.Sprintf("id=%v, ", at.ID)) + builder.WriteString("user_id=") + builder.WriteString(fmt.Sprintf("%v", at.UserID)) + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(at.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(at.UpdatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("action=") + builder.WriteString(fmt.Sprintf("%v", at.Action)) + builder.WriteString(", ") + builder.WriteString("token=") + builder.WriteString(fmt.Sprintf("%v", at.Token)) + builder.WriteByte(')') + return builder.String() +} + +// ActionTokens is a parsable slice of ActionToken. +type ActionTokens []*ActionToken diff --git a/backend/internal/data/ent/actiontoken/actiontoken.go b/backend/internal/data/ent/actiontoken/actiontoken.go new file mode 100644 index 00000000..e3b869db --- /dev/null +++ b/backend/internal/data/ent/actiontoken/actiontoken.go @@ -0,0 +1,138 @@ +// Code generated by ent, DO NOT EDIT. + +package actiontoken + +import ( + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/google/uuid" +) + +const ( + // Label holds the string label denoting the actiontoken type in the database. + Label = "action_token" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldUserID holds the string denoting the user_id field in the database. + FieldUserID = "user_id" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldAction holds the string denoting the action field in the database. + FieldAction = "action" + // FieldToken holds the string denoting the token field in the database. + FieldToken = "token" + // EdgeUser holds the string denoting the user edge name in mutations. + EdgeUser = "user" + // Table holds the table name of the actiontoken in the database. + Table = "action_tokens" + // UserTable is the table that holds the user relation/edge. + UserTable = "action_tokens" + // UserInverseTable is the table name for the User entity. + // It exists in this package in order to avoid circular dependency with the "user" package. + UserInverseTable = "users" + // UserColumn is the table column denoting the user relation/edge. + UserColumn = "user_id" +) + +// Columns holds all SQL columns for actiontoken fields. +var Columns = []string{ + FieldID, + FieldUserID, + FieldCreatedAt, + FieldUpdatedAt, + FieldAction, + FieldToken, +} + +// 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 + } + } + return false +} + +var ( + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() uuid.UUID +) + +// Action defines the type for the "action" enum field. +type Action string + +// ActionResetPassword is the default value of the Action enum. +const DefaultAction = ActionResetPassword + +// Action values. +const ( + ActionResetPassword Action = "reset_password" +) + +func (a Action) String() string { + return string(a) +} + +// ActionValidator is a validator for the "action" field enum values. It is called by the builders before save. +func ActionValidator(a Action) error { + switch a { + case ActionResetPassword: + return nil + default: + return fmt.Errorf("actiontoken: invalid enum value for action field: %q", a) + } +} + +// OrderOption defines the ordering options for the ActionToken queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByUserID orders the results by the user_id field. +func ByUserID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserID, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByAction orders the results by the action field. +func ByAction(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldAction, opts...).ToFunc() +} + +// ByUserField orders the results by user field. +func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...)) + } +} +func newUserStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(UserInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) +} diff --git a/backend/internal/data/ent/actiontoken/where.go b/backend/internal/data/ent/actiontoken/where.go new file mode 100644 index 00000000..2008f8af --- /dev/null +++ b/backend/internal/data/ent/actiontoken/where.go @@ -0,0 +1,275 @@ +// Code generated by ent, DO NOT EDIT. + +package actiontoken + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldLTE(FieldID, id)) +} + +// UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. +func UserID(v uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldUserID, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// Token applies equality check predicate on the "token" field. It's identical to TokenEQ. +func Token(v []byte) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldToken, v)) +} + +// UserIDEQ applies the EQ predicate on the "user_id" field. +func UserIDEQ(v uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldUserID, v)) +} + +// UserIDNEQ applies the NEQ predicate on the "user_id" field. +func UserIDNEQ(v uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNEQ(FieldUserID, v)) +} + +// UserIDIn applies the In predicate on the "user_id" field. +func UserIDIn(vs ...uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldIn(FieldUserID, vs...)) +} + +// UserIDNotIn applies the NotIn predicate on the "user_id" field. +func UserIDNotIn(vs ...uuid.UUID) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNotIn(FieldUserID, vs...)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.ActionToken { + return predicate.ActionToken(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// ActionEQ applies the EQ predicate on the "action" field. +func ActionEQ(v Action) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldAction, v)) +} + +// ActionNEQ applies the NEQ predicate on the "action" field. +func ActionNEQ(v Action) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNEQ(FieldAction, v)) +} + +// ActionIn applies the In predicate on the "action" field. +func ActionIn(vs ...Action) predicate.ActionToken { + return predicate.ActionToken(sql.FieldIn(FieldAction, vs...)) +} + +// ActionNotIn applies the NotIn predicate on the "action" field. +func ActionNotIn(vs ...Action) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNotIn(FieldAction, vs...)) +} + +// TokenEQ applies the EQ predicate on the "token" field. +func TokenEQ(v []byte) predicate.ActionToken { + return predicate.ActionToken(sql.FieldEQ(FieldToken, v)) +} + +// TokenNEQ applies the NEQ predicate on the "token" field. +func TokenNEQ(v []byte) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNEQ(FieldToken, v)) +} + +// TokenIn applies the In predicate on the "token" field. +func TokenIn(vs ...[]byte) predicate.ActionToken { + return predicate.ActionToken(sql.FieldIn(FieldToken, vs...)) +} + +// TokenNotIn applies the NotIn predicate on the "token" field. +func TokenNotIn(vs ...[]byte) predicate.ActionToken { + return predicate.ActionToken(sql.FieldNotIn(FieldToken, vs...)) +} + +// TokenGT applies the GT predicate on the "token" field. +func TokenGT(v []byte) predicate.ActionToken { + return predicate.ActionToken(sql.FieldGT(FieldToken, v)) +} + +// TokenGTE applies the GTE predicate on the "token" field. +func TokenGTE(v []byte) predicate.ActionToken { + return predicate.ActionToken(sql.FieldGTE(FieldToken, v)) +} + +// TokenLT applies the LT predicate on the "token" field. +func TokenLT(v []byte) predicate.ActionToken { + return predicate.ActionToken(sql.FieldLT(FieldToken, v)) +} + +// TokenLTE applies the LTE predicate on the "token" field. +func TokenLTE(v []byte) predicate.ActionToken { + return predicate.ActionToken(sql.FieldLTE(FieldToken, v)) +} + +// HasUser applies the HasEdge predicate on the "user" edge. +func HasUser() predicate.ActionToken { + return predicate.ActionToken(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasUserWith applies the HasEdge predicate on the "user" edge with a given conditions (other predicates). +func HasUserWith(preds ...predicate.User) predicate.ActionToken { + return predicate.ActionToken(func(s *sql.Selector) { + step := newUserStep() + 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.ActionToken) predicate.ActionToken { + return predicate.ActionToken(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.ActionToken) predicate.ActionToken { + return predicate.ActionToken(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.ActionToken) predicate.ActionToken { + return predicate.ActionToken(sql.NotPredicates(p)) +} diff --git a/backend/internal/data/ent/actiontoken_create.go b/backend/internal/data/ent/actiontoken_create.go new file mode 100644 index 00000000..6a285d49 --- /dev/null +++ b/backend/internal/data/ent/actiontoken_create.go @@ -0,0 +1,329 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken" + "github.com/hay-kot/homebox/backend/internal/data/ent/user" +) + +// ActionTokenCreate is the builder for creating a ActionToken entity. +type ActionTokenCreate struct { + config + mutation *ActionTokenMutation + hooks []Hook +} + +// SetUserID sets the "user_id" field. +func (atc *ActionTokenCreate) SetUserID(u uuid.UUID) *ActionTokenCreate { + atc.mutation.SetUserID(u) + return atc +} + +// SetCreatedAt sets the "created_at" field. +func (atc *ActionTokenCreate) SetCreatedAt(t time.Time) *ActionTokenCreate { + atc.mutation.SetCreatedAt(t) + return atc +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (atc *ActionTokenCreate) SetNillableCreatedAt(t *time.Time) *ActionTokenCreate { + if t != nil { + atc.SetCreatedAt(*t) + } + return atc +} + +// SetUpdatedAt sets the "updated_at" field. +func (atc *ActionTokenCreate) SetUpdatedAt(t time.Time) *ActionTokenCreate { + atc.mutation.SetUpdatedAt(t) + return atc +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (atc *ActionTokenCreate) SetNillableUpdatedAt(t *time.Time) *ActionTokenCreate { + if t != nil { + atc.SetUpdatedAt(*t) + } + return atc +} + +// SetAction sets the "action" field. +func (atc *ActionTokenCreate) SetAction(a actiontoken.Action) *ActionTokenCreate { + atc.mutation.SetAction(a) + return atc +} + +// SetNillableAction sets the "action" field if the given value is not nil. +func (atc *ActionTokenCreate) SetNillableAction(a *actiontoken.Action) *ActionTokenCreate { + if a != nil { + atc.SetAction(*a) + } + return atc +} + +// SetToken sets the "token" field. +func (atc *ActionTokenCreate) SetToken(b []byte) *ActionTokenCreate { + atc.mutation.SetToken(b) + return atc +} + +// SetID sets the "id" field. +func (atc *ActionTokenCreate) SetID(u uuid.UUID) *ActionTokenCreate { + atc.mutation.SetID(u) + return atc +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (atc *ActionTokenCreate) SetNillableID(u *uuid.UUID) *ActionTokenCreate { + if u != nil { + atc.SetID(*u) + } + return atc +} + +// SetUser sets the "user" edge to the User entity. +func (atc *ActionTokenCreate) SetUser(u *User) *ActionTokenCreate { + return atc.SetUserID(u.ID) +} + +// Mutation returns the ActionTokenMutation object of the builder. +func (atc *ActionTokenCreate) Mutation() *ActionTokenMutation { + return atc.mutation +} + +// Save creates the ActionToken in the database. +func (atc *ActionTokenCreate) Save(ctx context.Context) (*ActionToken, error) { + atc.defaults() + return withHooks(ctx, atc.sqlSave, atc.mutation, atc.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (atc *ActionTokenCreate) SaveX(ctx context.Context) *ActionToken { + v, err := atc.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (atc *ActionTokenCreate) Exec(ctx context.Context) error { + _, err := atc.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (atc *ActionTokenCreate) ExecX(ctx context.Context) { + if err := atc.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (atc *ActionTokenCreate) defaults() { + if _, ok := atc.mutation.CreatedAt(); !ok { + v := actiontoken.DefaultCreatedAt() + atc.mutation.SetCreatedAt(v) + } + if _, ok := atc.mutation.UpdatedAt(); !ok { + v := actiontoken.DefaultUpdatedAt() + atc.mutation.SetUpdatedAt(v) + } + if _, ok := atc.mutation.Action(); !ok { + v := actiontoken.DefaultAction + atc.mutation.SetAction(v) + } + if _, ok := atc.mutation.ID(); !ok { + v := actiontoken.DefaultID() + atc.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (atc *ActionTokenCreate) check() error { + if _, ok := atc.mutation.UserID(); !ok { + return &ValidationError{Name: "user_id", err: errors.New(`ent: missing required field "ActionToken.user_id"`)} + } + if _, ok := atc.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "ActionToken.created_at"`)} + } + if _, ok := atc.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "ActionToken.updated_at"`)} + } + if _, ok := atc.mutation.Action(); !ok { + return &ValidationError{Name: "action", err: errors.New(`ent: missing required field "ActionToken.action"`)} + } + if v, ok := atc.mutation.Action(); ok { + if err := actiontoken.ActionValidator(v); err != nil { + return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "ActionToken.action": %w`, err)} + } + } + if _, ok := atc.mutation.Token(); !ok { + return &ValidationError{Name: "token", err: errors.New(`ent: missing required field "ActionToken.token"`)} + } + if _, ok := atc.mutation.UserID(); !ok { + return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "ActionToken.user"`)} + } + return nil +} + +func (atc *ActionTokenCreate) sqlSave(ctx context.Context) (*ActionToken, error) { + if err := atc.check(); err != nil { + return nil, err + } + _node, _spec := atc.createSpec() + if err := sqlgraph.CreateNode(ctx, atc.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(*uuid.UUID); ok { + _node.ID = *id + } else if err := _node.ID.Scan(_spec.ID.Value); err != nil { + return nil, err + } + } + atc.mutation.id = &_node.ID + atc.mutation.done = true + return _node, nil +} + +func (atc *ActionTokenCreate) createSpec() (*ActionToken, *sqlgraph.CreateSpec) { + var ( + _node = &ActionToken{config: atc.config} + _spec = sqlgraph.NewCreateSpec(actiontoken.Table, sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID)) + ) + if id, ok := atc.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = &id + } + if value, ok := atc.mutation.CreatedAt(); ok { + _spec.SetField(actiontoken.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := atc.mutation.UpdatedAt(); ok { + _spec.SetField(actiontoken.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if value, ok := atc.mutation.Action(); ok { + _spec.SetField(actiontoken.FieldAction, field.TypeEnum, value) + _node.Action = value + } + if value, ok := atc.mutation.Token(); ok { + _spec.SetField(actiontoken.FieldToken, field.TypeBytes, value) + _node.Token = value + } + if nodes := atc.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: actiontoken.UserTable, + Columns: []string{actiontoken.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.UserID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// ActionTokenCreateBulk is the builder for creating many ActionToken entities in bulk. +type ActionTokenCreateBulk struct { + config + err error + builders []*ActionTokenCreate +} + +// Save creates the ActionToken entities in the database. +func (atcb *ActionTokenCreateBulk) Save(ctx context.Context) ([]*ActionToken, error) { + if atcb.err != nil { + return nil, atcb.err + } + specs := make([]*sqlgraph.CreateSpec, len(atcb.builders)) + nodes := make([]*ActionToken, len(atcb.builders)) + mutators := make([]Mutator, len(atcb.builders)) + for i := range atcb.builders { + func(i int, root context.Context) { + builder := atcb.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*ActionTokenMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, atcb.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, atcb.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 + 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, atcb.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (atcb *ActionTokenCreateBulk) SaveX(ctx context.Context) []*ActionToken { + v, err := atcb.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (atcb *ActionTokenCreateBulk) Exec(ctx context.Context) error { + _, err := atcb.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (atcb *ActionTokenCreateBulk) ExecX(ctx context.Context) { + if err := atcb.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/internal/data/ent/actiontoken_delete.go b/backend/internal/data/ent/actiontoken_delete.go new file mode 100644 index 00000000..7bc72767 --- /dev/null +++ b/backend/internal/data/ent/actiontoken_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "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/actiontoken" + "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" +) + +// ActionTokenDelete is the builder for deleting a ActionToken entity. +type ActionTokenDelete struct { + config + hooks []Hook + mutation *ActionTokenMutation +} + +// Where appends a list predicates to the ActionTokenDelete builder. +func (atd *ActionTokenDelete) Where(ps ...predicate.ActionToken) *ActionTokenDelete { + atd.mutation.Where(ps...) + return atd +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (atd *ActionTokenDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, atd.sqlExec, atd.mutation, atd.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (atd *ActionTokenDelete) ExecX(ctx context.Context) int { + n, err := atd.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (atd *ActionTokenDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(actiontoken.Table, sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID)) + if ps := atd.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, atd.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + atd.mutation.done = true + return affected, err +} + +// ActionTokenDeleteOne is the builder for deleting a single ActionToken entity. +type ActionTokenDeleteOne struct { + atd *ActionTokenDelete +} + +// Where appends a list predicates to the ActionTokenDelete builder. +func (atdo *ActionTokenDeleteOne) Where(ps ...predicate.ActionToken) *ActionTokenDeleteOne { + atdo.atd.mutation.Where(ps...) + return atdo +} + +// Exec executes the deletion query. +func (atdo *ActionTokenDeleteOne) Exec(ctx context.Context) error { + n, err := atdo.atd.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{actiontoken.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (atdo *ActionTokenDeleteOne) ExecX(ctx context.Context) { + if err := atdo.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/internal/data/ent/actiontoken_query.go b/backend/internal/data/ent/actiontoken_query.go new file mode 100644 index 00000000..77b30c6a --- /dev/null +++ b/backend/internal/data/ent/actiontoken_query.go @@ -0,0 +1,606 @@ +// 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/actiontoken" + "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" + "github.com/hay-kot/homebox/backend/internal/data/ent/user" +) + +// ActionTokenQuery is the builder for querying ActionToken entities. +type ActionTokenQuery struct { + config + ctx *QueryContext + order []actiontoken.OrderOption + inters []Interceptor + predicates []predicate.ActionToken + withUser *UserQuery + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the ActionTokenQuery builder. +func (atq *ActionTokenQuery) Where(ps ...predicate.ActionToken) *ActionTokenQuery { + atq.predicates = append(atq.predicates, ps...) + return atq +} + +// Limit the number of records to be returned by this query. +func (atq *ActionTokenQuery) Limit(limit int) *ActionTokenQuery { + atq.ctx.Limit = &limit + return atq +} + +// Offset to start from. +func (atq *ActionTokenQuery) Offset(offset int) *ActionTokenQuery { + atq.ctx.Offset = &offset + return atq +} + +// 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 (atq *ActionTokenQuery) Unique(unique bool) *ActionTokenQuery { + atq.ctx.Unique = &unique + return atq +} + +// Order specifies how the records should be ordered. +func (atq *ActionTokenQuery) Order(o ...actiontoken.OrderOption) *ActionTokenQuery { + atq.order = append(atq.order, o...) + return atq +} + +// QueryUser chains the current query on the "user" edge. +func (atq *ActionTokenQuery) QueryUser() *UserQuery { + query := (&UserClient{config: atq.config}).Query() + 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(actiontoken.Table, actiontoken.FieldID, selector), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, actiontoken.UserTable, actiontoken.UserColumn), + ) + fromU = sqlgraph.SetNeighbors(atq.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first ActionToken entity from the query. +// Returns a *NotFoundError when no ActionToken was found. +func (atq *ActionTokenQuery) First(ctx context.Context) (*ActionToken, error) { + nodes, err := atq.Limit(1).All(setContextOp(ctx, atq.ctx, "First")) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{actiontoken.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (atq *ActionTokenQuery) FirstX(ctx context.Context) *ActionToken { + node, err := atq.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first ActionToken ID from the query. +// Returns a *NotFoundError when no ActionToken ID was found. +func (atq *ActionTokenQuery) FirstID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = atq.Limit(1).IDs(setContextOp(ctx, atq.ctx, "FirstID")); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{actiontoken.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (atq *ActionTokenQuery) FirstIDX(ctx context.Context) uuid.UUID { + id, err := atq.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single ActionToken entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one ActionToken entity is found. +// Returns a *NotFoundError when no ActionToken entities are found. +func (atq *ActionTokenQuery) Only(ctx context.Context) (*ActionToken, error) { + nodes, err := atq.Limit(2).All(setContextOp(ctx, atq.ctx, "Only")) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{actiontoken.Label} + default: + return nil, &NotSingularError{actiontoken.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (atq *ActionTokenQuery) OnlyX(ctx context.Context) *ActionToken { + node, err := atq.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only ActionToken ID in the query. +// Returns a *NotSingularError when more than one ActionToken ID is found. +// Returns a *NotFoundError when no entities are found. +func (atq *ActionTokenQuery) OnlyID(ctx context.Context) (id uuid.UUID, err error) { + var ids []uuid.UUID + if ids, err = atq.Limit(2).IDs(setContextOp(ctx, atq.ctx, "OnlyID")); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{actiontoken.Label} + default: + err = &NotSingularError{actiontoken.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (atq *ActionTokenQuery) OnlyIDX(ctx context.Context) uuid.UUID { + id, err := atq.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of ActionTokens. +func (atq *ActionTokenQuery) All(ctx context.Context) ([]*ActionToken, error) { + ctx = setContextOp(ctx, atq.ctx, "All") + if err := atq.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*ActionToken, *ActionTokenQuery]() + return withInterceptors[[]*ActionToken](ctx, atq, qr, atq.inters) +} + +// AllX is like All, but panics if an error occurs. +func (atq *ActionTokenQuery) AllX(ctx context.Context) []*ActionToken { + nodes, err := atq.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of ActionToken IDs. +func (atq *ActionTokenQuery) IDs(ctx context.Context) (ids []uuid.UUID, err error) { + if atq.ctx.Unique == nil && atq.path != nil { + atq.Unique(true) + } + ctx = setContextOp(ctx, atq.ctx, "IDs") + if err = atq.Select(actiontoken.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (atq *ActionTokenQuery) IDsX(ctx context.Context) []uuid.UUID { + ids, err := atq.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (atq *ActionTokenQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, atq.ctx, "Count") + if err := atq.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, atq, querierCount[*ActionTokenQuery](), atq.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (atq *ActionTokenQuery) CountX(ctx context.Context) int { + count, err := atq.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (atq *ActionTokenQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, atq.ctx, "Exist") + switch _, err := atq.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (atq *ActionTokenQuery) ExistX(ctx context.Context) bool { + exist, err := atq.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the ActionTokenQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (atq *ActionTokenQuery) Clone() *ActionTokenQuery { + if atq == nil { + return nil + } + return &ActionTokenQuery{ + config: atq.config, + ctx: atq.ctx.Clone(), + order: append([]actiontoken.OrderOption{}, atq.order...), + inters: append([]Interceptor{}, atq.inters...), + predicates: append([]predicate.ActionToken{}, atq.predicates...), + withUser: atq.withUser.Clone(), + // clone intermediate query. + sql: atq.sql.Clone(), + path: atq.path, + } +} + +// WithUser tells the query-builder to eager-load the nodes that are connected to +// the "user" edge. The optional arguments are used to configure the query builder of the edge. +func (atq *ActionTokenQuery) WithUser(opts ...func(*UserQuery)) *ActionTokenQuery { + query := (&UserClient{config: atq.config}).Query() + for _, opt := range opts { + opt(query) + } + atq.withUser = 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. +// +// Example: +// +// var v []struct { +// UserID uuid.UUID `json:"user_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.ActionToken.Query(). +// GroupBy(actiontoken.FieldUserID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (atq *ActionTokenQuery) GroupBy(field string, fields ...string) *ActionTokenGroupBy { + atq.ctx.Fields = append([]string{field}, fields...) + grbuild := &ActionTokenGroupBy{build: atq} + grbuild.flds = &atq.ctx.Fields + grbuild.label = actiontoken.Label + grbuild.scan = 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 { +// UserID uuid.UUID `json:"user_id,omitempty"` +// } +// +// client.ActionToken.Query(). +// Select(actiontoken.FieldUserID). +// Scan(ctx, &v) +func (atq *ActionTokenQuery) Select(fields ...string) *ActionTokenSelect { + atq.ctx.Fields = append(atq.ctx.Fields, fields...) + sbuild := &ActionTokenSelect{ActionTokenQuery: atq} + sbuild.label = actiontoken.Label + sbuild.flds, sbuild.scan = &atq.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a ActionTokenSelect configured with the given aggregations. +func (atq *ActionTokenQuery) Aggregate(fns ...AggregateFunc) *ActionTokenSelect { + return atq.Select().Aggregate(fns...) +} + +func (atq *ActionTokenQuery) prepareQuery(ctx context.Context) error { + for _, inter := range atq.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, atq); err != nil { + return err + } + } + } + for _, f := range atq.ctx.Fields { + if !actiontoken.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if atq.path != nil { + prev, err := atq.path(ctx) + if err != nil { + return err + } + atq.sql = prev + } + return nil +} + +func (atq *ActionTokenQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ActionToken, error) { + var ( + nodes = []*ActionToken{} + _spec = atq.querySpec() + loadedTypes = [1]bool{ + atq.withUser != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*ActionToken).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &ActionToken{config: atq.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, atq.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := atq.withUser; query != nil { + if err := atq.loadUser(ctx, query, nodes, nil, + func(n *ActionToken, e *User) { n.Edges.User = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (atq *ActionTokenQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*ActionToken, init func(*ActionToken), assign func(*ActionToken, *User)) error { + ids := make([]uuid.UUID, 0, len(nodes)) + nodeids := make(map[uuid.UUID][]*ActionToken) + for i := range nodes { + fk := nodes[i].UserID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(user.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 "user_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (atq *ActionTokenQuery) sqlCount(ctx context.Context) (int, error) { + _spec := atq.querySpec() + _spec.Node.Columns = atq.ctx.Fields + if len(atq.ctx.Fields) > 0 { + _spec.Unique = atq.ctx.Unique != nil && *atq.ctx.Unique + } + return sqlgraph.CountNodes(ctx, atq.driver, _spec) +} + +func (atq *ActionTokenQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(actiontoken.Table, actiontoken.Columns, sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID)) + _spec.From = atq.sql + if unique := atq.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if atq.path != nil { + _spec.Unique = true + } + if fields := atq.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, actiontoken.FieldID) + for i := range fields { + if fields[i] != actiontoken.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if atq.withUser != nil { + _spec.Node.AddColumnOnce(actiontoken.FieldUserID) + } + } + if ps := atq.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := atq.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := atq.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := atq.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (atq *ActionTokenQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(atq.driver.Dialect()) + t1 := builder.Table(actiontoken.Table) + columns := atq.ctx.Fields + if len(columns) == 0 { + columns = actiontoken.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if atq.sql != nil { + selector = atq.sql + selector.Select(selector.Columns(columns...)...) + } + if atq.ctx.Unique != nil && *atq.ctx.Unique { + selector.Distinct() + } + for _, p := range atq.predicates { + p(selector) + } + for _, p := range atq.order { + p(selector) + } + if offset := atq.ctx.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 := atq.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ActionTokenGroupBy is the group-by builder for ActionToken entities. +type ActionTokenGroupBy struct { + selector + build *ActionTokenQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (atgb *ActionTokenGroupBy) Aggregate(fns ...AggregateFunc) *ActionTokenGroupBy { + atgb.fns = append(atgb.fns, fns...) + return atgb +} + +// Scan applies the selector query and scans the result into the given value. +func (atgb *ActionTokenGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, atgb.build.ctx, "GroupBy") + if err := atgb.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ActionTokenQuery, *ActionTokenGroupBy](ctx, atgb.build, atgb, atgb.build.inters, v) +} + +func (atgb *ActionTokenGroupBy) sqlScan(ctx context.Context, root *ActionTokenQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(atgb.fns)) + for _, fn := range atgb.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*atgb.flds)+len(atgb.fns)) + for _, f := range *atgb.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*atgb.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := atgb.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// ActionTokenSelect is the builder for selecting fields of ActionToken entities. +type ActionTokenSelect struct { + *ActionTokenQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (ats *ActionTokenSelect) Aggregate(fns ...AggregateFunc) *ActionTokenSelect { + ats.fns = append(ats.fns, fns...) + return ats +} + +// Scan applies the selector query and scans the result into the given value. +func (ats *ActionTokenSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, ats.ctx, "Select") + if err := ats.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*ActionTokenQuery, *ActionTokenSelect](ctx, ats.ActionTokenQuery, ats, ats.inters, v) +} + +func (ats *ActionTokenSelect) sqlScan(ctx context.Context, root *ActionTokenQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(ats.fns)) + for _, fn := range ats.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*ats.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := ats.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/actiontoken_update.go b/backend/internal/data/ent/actiontoken_update.go new file mode 100644 index 00000000..842d4861 --- /dev/null +++ b/backend/internal/data/ent/actiontoken_update.go @@ -0,0 +1,406 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "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/actiontoken" + "github.com/hay-kot/homebox/backend/internal/data/ent/predicate" + "github.com/hay-kot/homebox/backend/internal/data/ent/user" +) + +// ActionTokenUpdate is the builder for updating ActionToken entities. +type ActionTokenUpdate struct { + config + hooks []Hook + mutation *ActionTokenMutation +} + +// Where appends a list predicates to the ActionTokenUpdate builder. +func (atu *ActionTokenUpdate) Where(ps ...predicate.ActionToken) *ActionTokenUpdate { + atu.mutation.Where(ps...) + return atu +} + +// SetUserID sets the "user_id" field. +func (atu *ActionTokenUpdate) SetUserID(u uuid.UUID) *ActionTokenUpdate { + atu.mutation.SetUserID(u) + return atu +} + +// SetNillableUserID sets the "user_id" field if the given value is not nil. +func (atu *ActionTokenUpdate) SetNillableUserID(u *uuid.UUID) *ActionTokenUpdate { + if u != nil { + atu.SetUserID(*u) + } + return atu +} + +// SetUpdatedAt sets the "updated_at" field. +func (atu *ActionTokenUpdate) SetUpdatedAt(t time.Time) *ActionTokenUpdate { + atu.mutation.SetUpdatedAt(t) + return atu +} + +// SetAction sets the "action" field. +func (atu *ActionTokenUpdate) SetAction(a actiontoken.Action) *ActionTokenUpdate { + atu.mutation.SetAction(a) + return atu +} + +// SetNillableAction sets the "action" field if the given value is not nil. +func (atu *ActionTokenUpdate) SetNillableAction(a *actiontoken.Action) *ActionTokenUpdate { + if a != nil { + atu.SetAction(*a) + } + return atu +} + +// SetToken sets the "token" field. +func (atu *ActionTokenUpdate) SetToken(b []byte) *ActionTokenUpdate { + atu.mutation.SetToken(b) + return atu +} + +// SetUser sets the "user" edge to the User entity. +func (atu *ActionTokenUpdate) SetUser(u *User) *ActionTokenUpdate { + return atu.SetUserID(u.ID) +} + +// Mutation returns the ActionTokenMutation object of the builder. +func (atu *ActionTokenUpdate) Mutation() *ActionTokenMutation { + return atu.mutation +} + +// ClearUser clears the "user" edge to the User entity. +func (atu *ActionTokenUpdate) ClearUser() *ActionTokenUpdate { + atu.mutation.ClearUser() + return atu +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (atu *ActionTokenUpdate) Save(ctx context.Context) (int, error) { + atu.defaults() + return withHooks(ctx, atu.sqlSave, atu.mutation, atu.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (atu *ActionTokenUpdate) SaveX(ctx context.Context) int { + affected, err := atu.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (atu *ActionTokenUpdate) Exec(ctx context.Context) error { + _, err := atu.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (atu *ActionTokenUpdate) ExecX(ctx context.Context) { + if err := atu.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (atu *ActionTokenUpdate) defaults() { + if _, ok := atu.mutation.UpdatedAt(); !ok { + v := actiontoken.UpdateDefaultUpdatedAt() + atu.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (atu *ActionTokenUpdate) check() error { + if v, ok := atu.mutation.Action(); ok { + if err := actiontoken.ActionValidator(v); err != nil { + return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "ActionToken.action": %w`, err)} + } + } + if _, ok := atu.mutation.UserID(); atu.mutation.UserCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "ActionToken.user"`) + } + return nil +} + +func (atu *ActionTokenUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := atu.check(); err != nil { + return n, err + } + _spec := sqlgraph.NewUpdateSpec(actiontoken.Table, actiontoken.Columns, sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID)) + if ps := atu.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := atu.mutation.UpdatedAt(); ok { + _spec.SetField(actiontoken.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := atu.mutation.Action(); ok { + _spec.SetField(actiontoken.FieldAction, field.TypeEnum, value) + } + if value, ok := atu.mutation.Token(); ok { + _spec.SetField(actiontoken.FieldToken, field.TypeBytes, value) + } + if atu.mutation.UserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: actiontoken.UserTable, + Columns: []string{actiontoken.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := atu.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: actiontoken.UserTable, + Columns: []string{actiontoken.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), + }, + } + 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{actiontoken.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + atu.mutation.done = true + return n, nil +} + +// ActionTokenUpdateOne is the builder for updating a single ActionToken entity. +type ActionTokenUpdateOne struct { + config + fields []string + hooks []Hook + mutation *ActionTokenMutation +} + +// SetUserID sets the "user_id" field. +func (atuo *ActionTokenUpdateOne) SetUserID(u uuid.UUID) *ActionTokenUpdateOne { + atuo.mutation.SetUserID(u) + return atuo +} + +// SetNillableUserID sets the "user_id" field if the given value is not nil. +func (atuo *ActionTokenUpdateOne) SetNillableUserID(u *uuid.UUID) *ActionTokenUpdateOne { + if u != nil { + atuo.SetUserID(*u) + } + return atuo +} + +// SetUpdatedAt sets the "updated_at" field. +func (atuo *ActionTokenUpdateOne) SetUpdatedAt(t time.Time) *ActionTokenUpdateOne { + atuo.mutation.SetUpdatedAt(t) + return atuo +} + +// SetAction sets the "action" field. +func (atuo *ActionTokenUpdateOne) SetAction(a actiontoken.Action) *ActionTokenUpdateOne { + atuo.mutation.SetAction(a) + return atuo +} + +// SetNillableAction sets the "action" field if the given value is not nil. +func (atuo *ActionTokenUpdateOne) SetNillableAction(a *actiontoken.Action) *ActionTokenUpdateOne { + if a != nil { + atuo.SetAction(*a) + } + return atuo +} + +// SetToken sets the "token" field. +func (atuo *ActionTokenUpdateOne) SetToken(b []byte) *ActionTokenUpdateOne { + atuo.mutation.SetToken(b) + return atuo +} + +// SetUser sets the "user" edge to the User entity. +func (atuo *ActionTokenUpdateOne) SetUser(u *User) *ActionTokenUpdateOne { + return atuo.SetUserID(u.ID) +} + +// Mutation returns the ActionTokenMutation object of the builder. +func (atuo *ActionTokenUpdateOne) Mutation() *ActionTokenMutation { + return atuo.mutation +} + +// ClearUser clears the "user" edge to the User entity. +func (atuo *ActionTokenUpdateOne) ClearUser() *ActionTokenUpdateOne { + atuo.mutation.ClearUser() + return atuo +} + +// Where appends a list predicates to the ActionTokenUpdate builder. +func (atuo *ActionTokenUpdateOne) Where(ps ...predicate.ActionToken) *ActionTokenUpdateOne { + atuo.mutation.Where(ps...) + 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 *ActionTokenUpdateOne) Select(field string, fields ...string) *ActionTokenUpdateOne { + atuo.fields = append([]string{field}, fields...) + return atuo +} + +// Save executes the query and returns the updated ActionToken entity. +func (atuo *ActionTokenUpdateOne) Save(ctx context.Context) (*ActionToken, error) { + atuo.defaults() + return withHooks(ctx, atuo.sqlSave, atuo.mutation, atuo.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (atuo *ActionTokenUpdateOne) SaveX(ctx context.Context) *ActionToken { + node, err := atuo.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (atuo *ActionTokenUpdateOne) Exec(ctx context.Context) error { + _, err := atuo.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (atuo *ActionTokenUpdateOne) ExecX(ctx context.Context) { + if err := atuo.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (atuo *ActionTokenUpdateOne) defaults() { + if _, ok := atuo.mutation.UpdatedAt(); !ok { + v := actiontoken.UpdateDefaultUpdatedAt() + atuo.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (atuo *ActionTokenUpdateOne) check() error { + if v, ok := atuo.mutation.Action(); ok { + if err := actiontoken.ActionValidator(v); err != nil { + return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "ActionToken.action": %w`, err)} + } + } + if _, ok := atuo.mutation.UserID(); atuo.mutation.UserCleared() && !ok { + return errors.New(`ent: clearing a required unique edge "ActionToken.user"`) + } + return nil +} + +func (atuo *ActionTokenUpdateOne) sqlSave(ctx context.Context) (_node *ActionToken, err error) { + if err := atuo.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(actiontoken.Table, actiontoken.Columns, sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID)) + id, ok := atuo.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "ActionToken.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := atuo.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, actiontoken.FieldID) + for _, f := range fields { + if !actiontoken.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != actiontoken.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := atuo.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := atuo.mutation.UpdatedAt(); ok { + _spec.SetField(actiontoken.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := atuo.mutation.Action(); ok { + _spec.SetField(actiontoken.FieldAction, field.TypeEnum, value) + } + if value, ok := atuo.mutation.Token(); ok { + _spec.SetField(actiontoken.FieldToken, field.TypeBytes, value) + } + if atuo.mutation.UserCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: actiontoken.UserTable, + Columns: []string{actiontoken.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := atuo.mutation.UserIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: actiontoken.UserTable, + Columns: []string{actiontoken.UserColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(user.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &ActionToken{config: atuo.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, atuo.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{actiontoken.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + atuo.mutation.done = true + return _node, nil +} diff --git a/backend/internal/data/ent/client.go b/backend/internal/data/ent/client.go index 2fb9b539..f6ffec6b 100644 --- a/backend/internal/data/ent/client.go +++ b/backend/internal/data/ent/client.go @@ -16,6 +16,7 @@ import ( "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken" "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" @@ -36,6 +37,8 @@ type Client struct { config // Schema is the client for creating, migrating and dropping schema. Schema *migrate.Schema + // ActionToken is the client for interacting with the ActionToken builders. + ActionToken *ActionTokenClient // Attachment is the client for interacting with the Attachment builders. Attachment *AttachmentClient // AuthRoles is the client for interacting with the AuthRoles builders. @@ -73,6 +76,7 @@ func NewClient(opts ...Option) *Client { func (c *Client) init() { c.Schema = migrate.NewSchema(c.driver) + c.ActionToken = NewActionTokenClient(c.config) c.Attachment = NewAttachmentClient(c.config) c.AuthRoles = NewAuthRolesClient(c.config) c.AuthTokens = NewAuthTokensClient(c.config) @@ -178,6 +182,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { return &Tx{ ctx: ctx, config: cfg, + ActionToken: NewActionTokenClient(cfg), Attachment: NewAttachmentClient(cfg), AuthRoles: NewAuthRolesClient(cfg), AuthTokens: NewAuthTokensClient(cfg), @@ -210,6 +215,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) return &Tx{ ctx: ctx, config: cfg, + ActionToken: NewActionTokenClient(cfg), Attachment: NewAttachmentClient(cfg), AuthRoles: NewAuthRolesClient(cfg), AuthTokens: NewAuthTokensClient(cfg), @@ -229,7 +235,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) // Debug returns a new debug-client. It's used to get verbose logging on specific operations. // // client.Debug(). -// Attachment. +// ActionToken. // Query(). // Count(ctx) func (c *Client) Debug() *Client { @@ -252,7 +258,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) { for _, n := range []interface{ Use(...Hook) }{ - c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group, + c.ActionToken, c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group, c.GroupInvitationToken, c.Item, c.ItemField, c.Label, c.Location, c.MaintenanceEntry, c.Notifier, c.User, } { @@ -264,7 +270,7 @@ func (c *Client) Use(hooks ...Hook) { // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. func (c *Client) Intercept(interceptors ...Interceptor) { for _, n := range []interface{ Intercept(...Interceptor) }{ - c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group, + c.ActionToken, c.Attachment, c.AuthRoles, c.AuthTokens, c.Document, c.Group, c.GroupInvitationToken, c.Item, c.ItemField, c.Label, c.Location, c.MaintenanceEntry, c.Notifier, c.User, } { @@ -275,6 +281,8 @@ func (c *Client) Intercept(interceptors ...Interceptor) { // Mutate implements the ent.Mutator interface. func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { switch m := m.(type) { + case *ActionTokenMutation: + return c.ActionToken.mutate(ctx, m) case *AttachmentMutation: return c.Attachment.mutate(ctx, m) case *AuthRolesMutation: @@ -306,6 +314,155 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { } } +// ActionTokenClient is a client for the ActionToken schema. +type ActionTokenClient struct { + config +} + +// NewActionTokenClient returns a client for the ActionToken from the given config. +func NewActionTokenClient(c config) *ActionTokenClient { + return &ActionTokenClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `actiontoken.Hooks(f(g(h())))`. +func (c *ActionTokenClient) Use(hooks ...Hook) { + c.hooks.ActionToken = append(c.hooks.ActionToken, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `actiontoken.Intercept(f(g(h())))`. +func (c *ActionTokenClient) Intercept(interceptors ...Interceptor) { + c.inters.ActionToken = append(c.inters.ActionToken, interceptors...) +} + +// Create returns a builder for creating a ActionToken entity. +func (c *ActionTokenClient) Create() *ActionTokenCreate { + mutation := newActionTokenMutation(c.config, OpCreate) + return &ActionTokenCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of ActionToken entities. +func (c *ActionTokenClient) CreateBulk(builders ...*ActionTokenCreate) *ActionTokenCreateBulk { + return &ActionTokenCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *ActionTokenClient) MapCreateBulk(slice any, setFunc func(*ActionTokenCreate, int)) *ActionTokenCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &ActionTokenCreateBulk{err: fmt.Errorf("calling to ActionTokenClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*ActionTokenCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &ActionTokenCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for ActionToken. +func (c *ActionTokenClient) Update() *ActionTokenUpdate { + mutation := newActionTokenMutation(c.config, OpUpdate) + return &ActionTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *ActionTokenClient) UpdateOne(at *ActionToken) *ActionTokenUpdateOne { + mutation := newActionTokenMutation(c.config, OpUpdateOne, withActionToken(at)) + return &ActionTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *ActionTokenClient) UpdateOneID(id uuid.UUID) *ActionTokenUpdateOne { + mutation := newActionTokenMutation(c.config, OpUpdateOne, withActionTokenID(id)) + return &ActionTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for ActionToken. +func (c *ActionTokenClient) Delete() *ActionTokenDelete { + mutation := newActionTokenMutation(c.config, OpDelete) + return &ActionTokenDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *ActionTokenClient) DeleteOne(at *ActionToken) *ActionTokenDeleteOne { + return c.DeleteOneID(at.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *ActionTokenClient) DeleteOneID(id uuid.UUID) *ActionTokenDeleteOne { + builder := c.Delete().Where(actiontoken.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &ActionTokenDeleteOne{builder} +} + +// Query returns a query builder for ActionToken. +func (c *ActionTokenClient) Query() *ActionTokenQuery { + return &ActionTokenQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeActionToken}, + inters: c.Interceptors(), + } +} + +// Get returns a ActionToken entity by its id. +func (c *ActionTokenClient) Get(ctx context.Context, id uuid.UUID) (*ActionToken, error) { + return c.Query().Where(actiontoken.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *ActionTokenClient) GetX(ctx context.Context, id uuid.UUID) *ActionToken { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUser queries the user edge of a ActionToken. +func (c *ActionTokenClient) QueryUser(at *ActionToken) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := at.ID + step := sqlgraph.NewStep( + sqlgraph.From(actiontoken.Table, actiontoken.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, actiontoken.UserTable, actiontoken.UserColumn), + ) + fromV = sqlgraph.Neighbors(at.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *ActionTokenClient) Hooks() []Hook { + return c.hooks.ActionToken +} + +// Interceptors returns the client interceptors. +func (c *ActionTokenClient) Interceptors() []Interceptor { + return c.inters.ActionToken +} + +func (c *ActionTokenClient) mutate(ctx context.Context, m *ActionTokenMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&ActionTokenCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&ActionTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&ActionTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&ActionTokenDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown ActionToken mutation op: %q", m.Op()) + } +} + // AttachmentClient is a client for the Attachment schema. type AttachmentClient struct { config @@ -2586,6 +2743,22 @@ func (c *UserClient) QueryNotifiers(u *User) *NotifierQuery { return query } +// QueryActionTokens queries the action_tokens edge of a User. +func (c *UserClient) QueryActionTokens(u *User) *ActionTokenQuery { + query := (&ActionTokenClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := u.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(actiontoken.Table, actiontoken.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.ActionTokensTable, user.ActionTokensColumn), + ) + fromV = sqlgraph.Neighbors(u.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *UserClient) Hooks() []Hook { return c.hooks.User @@ -2614,11 +2787,13 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error) // hooks and interceptors per client, for fast access. type ( hooks struct { - Attachment, AuthRoles, AuthTokens, Document, Group, GroupInvitationToken, Item, - ItemField, Label, Location, MaintenanceEntry, Notifier, User []ent.Hook + ActionToken, Attachment, AuthRoles, AuthTokens, Document, Group, + GroupInvitationToken, Item, ItemField, Label, Location, MaintenanceEntry, + Notifier, User []ent.Hook } inters struct { - Attachment, AuthRoles, AuthTokens, Document, Group, GroupInvitationToken, Item, - ItemField, Label, Location, MaintenanceEntry, Notifier, User []ent.Interceptor + ActionToken, Attachment, AuthRoles, AuthTokens, Document, Group, + GroupInvitationToken, Item, ItemField, Label, Location, MaintenanceEntry, + Notifier, User []ent.Interceptor } ) diff --git a/backend/internal/data/ent/ent.go b/backend/internal/data/ent/ent.go index 6e52ac8b..1f94676f 100644 --- a/backend/internal/data/ent/ent.go +++ b/backend/internal/data/ent/ent.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken" "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" @@ -85,6 +86,7 @@ var ( func checkColumn(table, column string) error { initCheck.Do(func() { columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ + actiontoken.Table: actiontoken.ValidColumn, attachment.Table: attachment.ValidColumn, authroles.Table: authroles.ValidColumn, authtokens.Table: authtokens.ValidColumn, diff --git a/backend/internal/data/ent/has_id.go b/backend/internal/data/ent/has_id.go index 0877caac..28cf1435 100644 --- a/backend/internal/data/ent/has_id.go +++ b/backend/internal/data/ent/has_id.go @@ -4,6 +4,10 @@ package ent import "github.com/google/uuid" +func (at *ActionToken) GetID() uuid.UUID { + return at.ID +} + func (a *Attachment) GetID() uuid.UUID { return a.ID } diff --git a/backend/internal/data/ent/hook/hook.go b/backend/internal/data/ent/hook/hook.go index 4648b23a..6847b463 100644 --- a/backend/internal/data/ent/hook/hook.go +++ b/backend/internal/data/ent/hook/hook.go @@ -9,6 +9,18 @@ import ( "github.com/hay-kot/homebox/backend/internal/data/ent" ) +// The ActionTokenFunc type is an adapter to allow the use of ordinary +// function as ActionToken mutator. +type ActionTokenFunc func(context.Context, *ent.ActionTokenMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f ActionTokenFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.ActionTokenMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ActionTokenMutation", m) +} + // The AttachmentFunc type is an adapter to allow the use of ordinary // function as Attachment mutator. type AttachmentFunc func(context.Context, *ent.AttachmentMutation) (ent.Value, error) diff --git a/backend/internal/data/ent/migrate/schema.go b/backend/internal/data/ent/migrate/schema.go index 2b588380..c5c15fb2 100644 --- a/backend/internal/data/ent/migrate/schema.go +++ b/backend/internal/data/ent/migrate/schema.go @@ -8,6 +8,46 @@ import ( ) var ( + // ActionTokensColumns holds the columns for the "action_tokens" table. + ActionTokensColumns = []*schema.Column{ + {Name: "id", Type: field.TypeUUID}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + {Name: "action", Type: field.TypeEnum, Enums: []string{"reset_password"}, Default: "reset_password"}, + {Name: "token", Type: field.TypeBytes, Unique: true}, + {Name: "user_id", Type: field.TypeUUID}, + } + // ActionTokensTable holds the schema information for the "action_tokens" table. + ActionTokensTable = &schema.Table{ + Name: "action_tokens", + Columns: ActionTokensColumns, + PrimaryKey: []*schema.Column{ActionTokensColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "action_tokens_users_action_tokens", + Columns: []*schema.Column{ActionTokensColumns[5]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.Cascade, + }, + }, + Indexes: []*schema.Index{ + { + Name: "actiontoken_token", + Unique: false, + Columns: []*schema.Column{ActionTokensColumns[4]}, + }, + { + Name: "actiontoken_action", + Unique: false, + Columns: []*schema.Column{ActionTokensColumns[3]}, + }, + { + Name: "actiontoken_user_id", + Unique: false, + Columns: []*schema.Column{ActionTokensColumns[5]}, + }, + }, + } // AttachmentsColumns holds the columns for the "attachments" table. AttachmentsColumns = []*schema.Column{ {Name: "id", Type: field.TypeUUID}, @@ -453,6 +493,7 @@ var ( } // Tables holds all the tables in the schema. Tables = []*schema.Table{ + ActionTokensTable, AttachmentsTable, AuthRolesTable, AuthTokensTable, @@ -471,6 +512,7 @@ var ( ) func init() { + ActionTokensTable.ForeignKeys[0].RefTable = UsersTable AttachmentsTable.ForeignKeys[0].RefTable = DocumentsTable AttachmentsTable.ForeignKeys[1].RefTable = ItemsTable AuthRolesTable.ForeignKeys[0].RefTable = AuthTokensTable diff --git a/backend/internal/data/ent/mutation.go b/backend/internal/data/ent/mutation.go index 6fa15d3b..734c3524 100644 --- a/backend/internal/data/ent/mutation.go +++ b/backend/internal/data/ent/mutation.go @@ -12,6 +12,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken" "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" @@ -37,6 +38,7 @@ const ( OpUpdateOne = ent.OpUpdateOne // Node types. + TypeActionToken = "ActionToken" TypeAttachment = "Attachment" TypeAuthRoles = "AuthRoles" TypeAuthTokens = "AuthTokens" @@ -52,6 +54,608 @@ const ( TypeUser = "User" ) +// ActionTokenMutation represents an operation that mutates the ActionToken nodes in the graph. +type ActionTokenMutation struct { + config + op Op + typ string + id *uuid.UUID + created_at *time.Time + updated_at *time.Time + action *actiontoken.Action + token *[]byte + clearedFields map[string]struct{} + user *uuid.UUID + cleareduser bool + done bool + oldValue func(context.Context) (*ActionToken, error) + predicates []predicate.ActionToken +} + +var _ ent.Mutation = (*ActionTokenMutation)(nil) + +// actiontokenOption allows management of the mutation configuration using functional options. +type actiontokenOption func(*ActionTokenMutation) + +// newActionTokenMutation creates new mutation for the ActionToken entity. +func newActionTokenMutation(c config, op Op, opts ...actiontokenOption) *ActionTokenMutation { + m := &ActionTokenMutation{ + config: c, + op: op, + typ: TypeActionToken, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withActionTokenID sets the ID field of the mutation. +func withActionTokenID(id uuid.UUID) actiontokenOption { + return func(m *ActionTokenMutation) { + var ( + err error + once sync.Once + value *ActionToken + ) + m.oldValue = func(ctx context.Context) (*ActionToken, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().ActionToken.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withActionToken sets the old ActionToken of the mutation. +func withActionToken(node *ActionToken) actiontokenOption { + return func(m *ActionTokenMutation) { + m.oldValue = func(context.Context) (*ActionToken, 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 ActionTokenMutation) 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 ActionTokenMutation) 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 +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of ActionToken entities. +func (m *ActionTokenMutation) SetID(id uuid.UUID) { + m.id = &id +} + +// 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 *ActionTokenMutation) ID() (id uuid.UUID, 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 *ActionTokenMutation) IDs(ctx context.Context) ([]uuid.UUID, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []uuid.UUID{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().ActionToken.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetUserID sets the "user_id" field. +func (m *ActionTokenMutation) SetUserID(u uuid.UUID) { + m.user = &u +} + +// UserID returns the value of the "user_id" field in the mutation. +func (m *ActionTokenMutation) UserID() (r uuid.UUID, exists bool) { + v := m.user + if v == nil { + return + } + return *v, true +} + +// OldUserID returns the old "user_id" field's value of the ActionToken entity. +// If the ActionToken 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 *ActionTokenMutation) OldUserID(ctx context.Context) (v uuid.UUID, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserID: %w", err) + } + return oldValue.UserID, nil +} + +// ResetUserID resets all changes to the "user_id" field. +func (m *ActionTokenMutation) ResetUserID() { + m.user = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *ActionTokenMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *ActionTokenMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the ActionToken entity. +// If the ActionToken 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 *ActionTokenMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *ActionTokenMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *ActionTokenMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *ActionTokenMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the ActionToken entity. +// If the ActionToken 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 *ActionTokenMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *ActionTokenMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// SetAction sets the "action" field. +func (m *ActionTokenMutation) SetAction(a actiontoken.Action) { + m.action = &a +} + +// Action returns the value of the "action" field in the mutation. +func (m *ActionTokenMutation) Action() (r actiontoken.Action, exists bool) { + v := m.action + if v == nil { + return + } + return *v, true +} + +// OldAction returns the old "action" field's value of the ActionToken entity. +// If the ActionToken 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 *ActionTokenMutation) OldAction(ctx context.Context) (v actiontoken.Action, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldAction is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldAction requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldAction: %w", err) + } + return oldValue.Action, nil +} + +// ResetAction resets all changes to the "action" field. +func (m *ActionTokenMutation) ResetAction() { + m.action = nil +} + +// SetToken sets the "token" field. +func (m *ActionTokenMutation) SetToken(b []byte) { + m.token = &b +} + +// Token returns the value of the "token" field in the mutation. +func (m *ActionTokenMutation) Token() (r []byte, exists bool) { + v := m.token + if v == nil { + return + } + return *v, true +} + +// OldToken returns the old "token" field's value of the ActionToken entity. +// If the ActionToken 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 *ActionTokenMutation) OldToken(ctx context.Context) (v []byte, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldToken is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldToken requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldToken: %w", err) + } + return oldValue.Token, nil +} + +// ResetToken resets all changes to the "token" field. +func (m *ActionTokenMutation) ResetToken() { + m.token = nil +} + +// ClearUser clears the "user" edge to the User entity. +func (m *ActionTokenMutation) ClearUser() { + m.cleareduser = true + m.clearedFields[actiontoken.FieldUserID] = struct{}{} +} + +// UserCleared reports if the "user" edge to the User entity was cleared. +func (m *ActionTokenMutation) UserCleared() bool { + return m.cleareduser +} + +// UserIDs returns the "user" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// UserID instead. It exists only for internal usage by the builders. +func (m *ActionTokenMutation) UserIDs() (ids []uuid.UUID) { + if id := m.user; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetUser resets all changes to the "user" edge. +func (m *ActionTokenMutation) ResetUser() { + m.user = nil + m.cleareduser = false +} + +// Where appends a list predicates to the ActionTokenMutation builder. +func (m *ActionTokenMutation) Where(ps ...predicate.ActionToken) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the ActionTokenMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *ActionTokenMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.ActionToken, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *ActionTokenMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *ActionTokenMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (ActionToken). +func (m *ActionTokenMutation) 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 *ActionTokenMutation) Fields() []string { + fields := make([]string, 0, 5) + if m.user != nil { + fields = append(fields, actiontoken.FieldUserID) + } + if m.created_at != nil { + fields = append(fields, actiontoken.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, actiontoken.FieldUpdatedAt) + } + if m.action != nil { + fields = append(fields, actiontoken.FieldAction) + } + if m.token != nil { + fields = append(fields, actiontoken.FieldToken) + } + 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 *ActionTokenMutation) Field(name string) (ent.Value, bool) { + switch name { + case actiontoken.FieldUserID: + return m.UserID() + case actiontoken.FieldCreatedAt: + return m.CreatedAt() + case actiontoken.FieldUpdatedAt: + return m.UpdatedAt() + case actiontoken.FieldAction: + return m.Action() + case actiontoken.FieldToken: + return m.Token() + } + 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 *ActionTokenMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case actiontoken.FieldUserID: + return m.OldUserID(ctx) + case actiontoken.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case actiontoken.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case actiontoken.FieldAction: + return m.OldAction(ctx) + case actiontoken.FieldToken: + return m.OldToken(ctx) + } + return nil, fmt.Errorf("unknown ActionToken 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 *ActionTokenMutation) SetField(name string, value ent.Value) error { + switch name { + case actiontoken.FieldUserID: + v, ok := value.(uuid.UUID) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserID(v) + return nil + case actiontoken.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case actiontoken.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case actiontoken.FieldAction: + v, ok := value.(actiontoken.Action) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetAction(v) + return nil + case actiontoken.FieldToken: + v, ok := value.([]byte) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetToken(v) + return nil + } + return fmt.Errorf("unknown ActionToken field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *ActionTokenMutation) 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 *ActionTokenMutation) 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 *ActionTokenMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown ActionToken numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *ActionTokenMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *ActionTokenMutation) 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 *ActionTokenMutation) ClearField(name string) error { + return fmt.Errorf("unknown ActionToken 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 *ActionTokenMutation) ResetField(name string) error { + switch name { + case actiontoken.FieldUserID: + m.ResetUserID() + return nil + case actiontoken.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case actiontoken.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case actiontoken.FieldAction: + m.ResetAction() + return nil + case actiontoken.FieldToken: + m.ResetToken() + return nil + } + return fmt.Errorf("unknown ActionToken field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *ActionTokenMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.user != nil { + edges = append(edges, actiontoken.EdgeUser) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *ActionTokenMutation) AddedIDs(name string) []ent.Value { + switch name { + case actiontoken.EdgeUser: + if id := m.user; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *ActionTokenMutation) 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 *ActionTokenMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *ActionTokenMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.cleareduser { + edges = append(edges, actiontoken.EdgeUser) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *ActionTokenMutation) EdgeCleared(name string) bool { + switch name { + case actiontoken.EdgeUser: + return m.cleareduser + } + 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 *ActionTokenMutation) ClearEdge(name string) error { + switch name { + case actiontoken.EdgeUser: + m.ClearUser() + return nil + } + return fmt.Errorf("unknown ActionToken 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 *ActionTokenMutation) ResetEdge(name string) error { + switch name { + case actiontoken.EdgeUser: + m.ResetUser() + return nil + } + return fmt.Errorf("unknown ActionToken edge %s", name) +} + // AttachmentMutation represents an operation that mutates the Attachment nodes in the graph. type AttachmentMutation struct { config @@ -10672,30 +11276,33 @@ func (m *NotifierMutation) ResetEdge(name string) error { // UserMutation represents an operation that mutates the User nodes in the graph. type UserMutation struct { config - op Op - typ string - id *uuid.UUID - created_at *time.Time - updated_at *time.Time - name *string - email *string - password *string - is_superuser *bool - superuser *bool - role *user.Role - activated_on *time.Time - clearedFields map[string]struct{} - group *uuid.UUID - clearedgroup bool - auth_tokens map[uuid.UUID]struct{} - removedauth_tokens map[uuid.UUID]struct{} - clearedauth_tokens bool - notifiers map[uuid.UUID]struct{} - removednotifiers map[uuid.UUID]struct{} - clearednotifiers bool - done bool - oldValue func(context.Context) (*User, error) - predicates []predicate.User + op Op + typ string + id *uuid.UUID + created_at *time.Time + updated_at *time.Time + name *string + email *string + password *string + is_superuser *bool + superuser *bool + role *user.Role + activated_on *time.Time + clearedFields map[string]struct{} + group *uuid.UUID + clearedgroup bool + auth_tokens map[uuid.UUID]struct{} + removedauth_tokens map[uuid.UUID]struct{} + clearedauth_tokens bool + notifiers map[uuid.UUID]struct{} + removednotifiers map[uuid.UUID]struct{} + clearednotifiers bool + action_tokens map[uuid.UUID]struct{} + removedaction_tokens map[uuid.UUID]struct{} + clearedaction_tokens bool + done bool + oldValue func(context.Context) (*User, error) + predicates []predicate.User } var _ ent.Mutation = (*UserMutation)(nil) @@ -11286,6 +11893,60 @@ func (m *UserMutation) ResetNotifiers() { m.removednotifiers = nil } +// AddActionTokenIDs adds the "action_tokens" edge to the ActionToken entity by ids. +func (m *UserMutation) AddActionTokenIDs(ids ...uuid.UUID) { + if m.action_tokens == nil { + m.action_tokens = make(map[uuid.UUID]struct{}) + } + for i := range ids { + m.action_tokens[ids[i]] = struct{}{} + } +} + +// ClearActionTokens clears the "action_tokens" edge to the ActionToken entity. +func (m *UserMutation) ClearActionTokens() { + m.clearedaction_tokens = true +} + +// ActionTokensCleared reports if the "action_tokens" edge to the ActionToken entity was cleared. +func (m *UserMutation) ActionTokensCleared() bool { + return m.clearedaction_tokens +} + +// RemoveActionTokenIDs removes the "action_tokens" edge to the ActionToken entity by IDs. +func (m *UserMutation) RemoveActionTokenIDs(ids ...uuid.UUID) { + if m.removedaction_tokens == nil { + m.removedaction_tokens = make(map[uuid.UUID]struct{}) + } + for i := range ids { + delete(m.action_tokens, ids[i]) + m.removedaction_tokens[ids[i]] = struct{}{} + } +} + +// RemovedActionTokens returns the removed IDs of the "action_tokens" edge to the ActionToken entity. +func (m *UserMutation) RemovedActionTokensIDs() (ids []uuid.UUID) { + for id := range m.removedaction_tokens { + ids = append(ids, id) + } + return +} + +// ActionTokensIDs returns the "action_tokens" edge IDs in the mutation. +func (m *UserMutation) ActionTokensIDs() (ids []uuid.UUID) { + for id := range m.action_tokens { + ids = append(ids, id) + } + return +} + +// ResetActionTokens resets all changes to the "action_tokens" edge. +func (m *UserMutation) ResetActionTokens() { + m.action_tokens = nil + m.clearedaction_tokens = false + m.removedaction_tokens = nil +} + // Where appends a list predicates to the UserMutation builder. func (m *UserMutation) Where(ps ...predicate.User) { m.predicates = append(m.predicates, ps...) @@ -11564,7 +12225,7 @@ func (m *UserMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *UserMutation) AddedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.group != nil { edges = append(edges, user.EdgeGroup) } @@ -11574,6 +12235,9 @@ func (m *UserMutation) AddedEdges() []string { if m.notifiers != nil { edges = append(edges, user.EdgeNotifiers) } + if m.action_tokens != nil { + edges = append(edges, user.EdgeActionTokens) + } return edges } @@ -11597,19 +12261,28 @@ func (m *UserMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case user.EdgeActionTokens: + ids := make([]ent.Value, 0, len(m.action_tokens)) + for id := range m.action_tokens { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *UserMutation) RemovedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.removedauth_tokens != nil { edges = append(edges, user.EdgeAuthTokens) } if m.removednotifiers != nil { edges = append(edges, user.EdgeNotifiers) } + if m.removedaction_tokens != nil { + edges = append(edges, user.EdgeActionTokens) + } return edges } @@ -11629,13 +12302,19 @@ func (m *UserMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case user.EdgeActionTokens: + ids := make([]ent.Value, 0, len(m.removedaction_tokens)) + for id := range m.removedaction_tokens { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *UserMutation) ClearedEdges() []string { - edges := make([]string, 0, 3) + edges := make([]string, 0, 4) if m.clearedgroup { edges = append(edges, user.EdgeGroup) } @@ -11645,6 +12324,9 @@ func (m *UserMutation) ClearedEdges() []string { if m.clearednotifiers { edges = append(edges, user.EdgeNotifiers) } + if m.clearedaction_tokens { + edges = append(edges, user.EdgeActionTokens) + } return edges } @@ -11658,6 +12340,8 @@ func (m *UserMutation) EdgeCleared(name string) bool { return m.clearedauth_tokens case user.EdgeNotifiers: return m.clearednotifiers + case user.EdgeActionTokens: + return m.clearedaction_tokens } return false } @@ -11686,6 +12370,9 @@ func (m *UserMutation) ResetEdge(name string) error { case user.EdgeNotifiers: m.ResetNotifiers() return nil + case user.EdgeActionTokens: + m.ResetActionTokens() + return nil } return fmt.Errorf("unknown User edge %s", name) } diff --git a/backend/internal/data/ent/predicate/predicate.go b/backend/internal/data/ent/predicate/predicate.go index bd36616e..a3efa80a 100644 --- a/backend/internal/data/ent/predicate/predicate.go +++ b/backend/internal/data/ent/predicate/predicate.go @@ -6,6 +6,9 @@ import ( "entgo.io/ent/dialect/sql" ) +// ActionToken is the predicate function for actiontoken builders. +type ActionToken func(*sql.Selector) + // Attachment is the predicate function for attachment builders. type Attachment func(*sql.Selector) diff --git a/backend/internal/data/ent/runtime.go b/backend/internal/data/ent/runtime.go index c3aff001..b08b819c 100644 --- a/backend/internal/data/ent/runtime.go +++ b/backend/internal/data/ent/runtime.go @@ -6,6 +6,7 @@ import ( "time" "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken" "github.com/hay-kot/homebox/backend/internal/data/ent/attachment" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/document" @@ -25,6 +26,25 @@ import ( // (default values, validators, hooks and policies) and stitches it // to their package variables. func init() { + actiontokenMixin := schema.ActionToken{}.Mixin() + actiontokenMixinFields1 := actiontokenMixin[1].Fields() + _ = actiontokenMixinFields1 + actiontokenFields := schema.ActionToken{}.Fields() + _ = actiontokenFields + // actiontokenDescCreatedAt is the schema descriptor for created_at field. + actiontokenDescCreatedAt := actiontokenMixinFields1[1].Descriptor() + // actiontoken.DefaultCreatedAt holds the default value on creation for the created_at field. + actiontoken.DefaultCreatedAt = actiontokenDescCreatedAt.Default.(func() time.Time) + // actiontokenDescUpdatedAt is the schema descriptor for updated_at field. + actiontokenDescUpdatedAt := actiontokenMixinFields1[2].Descriptor() + // actiontoken.DefaultUpdatedAt holds the default value on creation for the updated_at field. + actiontoken.DefaultUpdatedAt = actiontokenDescUpdatedAt.Default.(func() time.Time) + // actiontoken.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + actiontoken.UpdateDefaultUpdatedAt = actiontokenDescUpdatedAt.UpdateDefault.(func() time.Time) + // actiontokenDescID is the schema descriptor for id field. + actiontokenDescID := actiontokenMixinFields1[0].Descriptor() + // actiontoken.DefaultID holds the default value on creation for the id field. + actiontoken.DefaultID = actiontokenDescID.Default.(func() uuid.UUID) attachmentMixin := schema.Attachment{}.Mixin() attachmentMixinFields0 := attachmentMixin[0].Fields() _ = attachmentMixinFields0 diff --git a/backend/internal/data/ent/schema/actiontoken.go b/backend/internal/data/ent/schema/actiontoken.go new file mode 100644 index 00000000..406dc04d --- /dev/null +++ b/backend/internal/data/ent/schema/actiontoken.go @@ -0,0 +1,42 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + + "github.com/hay-kot/homebox/backend/internal/data/ent/schema/mixins" +) + +type ActionToken struct { + ent.Schema +} + +func (ActionToken) Mixin() []ent.Mixin { + return []ent.Mixin{ + UserMixin{ + ref: "action_tokens", + field: "user_id", + }, + mixins.BaseMixin{}, + } +} + +// Fields of the ActionToken. +func (ActionToken) Fields() []ent.Field { + return []ent.Field{ + field.Enum("action"). + Values("reset_password"). + Default("reset_password"), + field.Bytes("token"). + Unique(), + } +} + +func (ActionToken) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("token"), + index.Fields("action"), + index.Fields("user_id"), + } +} diff --git a/backend/internal/data/ent/schema/user.go b/backend/internal/data/ent/schema/user.go index 10b0a8a9..88994afc 100644 --- a/backend/internal/data/ent/schema/user.go +++ b/backend/internal/data/ent/schema/user.go @@ -52,13 +52,11 @@ func (User) Fields() []ent.Field { func (User) Edges() []ent.Edge { return []ent.Edge{ edge.To("auth_tokens", AuthTokens.Type). - Annotations(entsql.Annotation{ - OnDelete: entsql.Cascade, - }), + Annotations(entsql.Annotation{OnDelete: entsql.Cascade}), edge.To("notifiers", Notifier.Type). - Annotations(entsql.Annotation{ - OnDelete: entsql.Cascade, - }), + Annotations(entsql.Annotation{OnDelete: entsql.Cascade}), + edge.To("action_tokens", ActionToken.Type). + Annotations(entsql.Annotation{OnDelete: entsql.Cascade}), } } diff --git a/backend/internal/data/ent/tx.go b/backend/internal/data/ent/tx.go index f51f2ac7..d2910ed6 100644 --- a/backend/internal/data/ent/tx.go +++ b/backend/internal/data/ent/tx.go @@ -12,6 +12,8 @@ import ( // Tx is a transactional client that is created by calling Client.Tx(). type Tx struct { config + // ActionToken is the client for interacting with the ActionToken builders. + ActionToken *ActionTokenClient // Attachment is the client for interacting with the Attachment builders. Attachment *AttachmentClient // AuthRoles is the client for interacting with the AuthRoles builders. @@ -169,6 +171,7 @@ func (tx *Tx) Client() *Client { } func (tx *Tx) init() { + tx.ActionToken = NewActionTokenClient(tx.config) tx.Attachment = NewAttachmentClient(tx.config) tx.AuthRoles = NewAuthRolesClient(tx.config) tx.AuthTokens = NewAuthTokensClient(tx.config) @@ -191,7 +194,7 @@ func (tx *Tx) init() { // of them in order to commit or rollback the transaction. // // If a closed transaction is embedded in one of the generated entities, and the entity -// applies a query, for example: Attachment.QueryXXX(), the query will be executed +// applies a query, for example: ActionToken.QueryXXX(), the query will be executed // through the driver which created this transaction. // // Note that txDriver is not goroutine safe. diff --git a/backend/internal/data/ent/user.go b/backend/internal/data/ent/user.go index 3331de75..b8a55981 100644 --- a/backend/internal/data/ent/user.go +++ b/backend/internal/data/ent/user.go @@ -52,9 +52,11 @@ type UserEdges struct { AuthTokens []*AuthTokens `json:"auth_tokens,omitempty"` // Notifiers holds the value of the notifiers edge. Notifiers []*Notifier `json:"notifiers,omitempty"` + // ActionTokens holds the value of the action_tokens edge. + ActionTokens []*ActionToken `json:"action_tokens,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [3]bool + loadedTypes [4]bool } // GroupOrErr returns the Group value or an error if the edge @@ -88,6 +90,15 @@ func (e UserEdges) NotifiersOrErr() ([]*Notifier, error) { return nil, &NotLoadedError{edge: "notifiers"} } +// ActionTokensOrErr returns the ActionTokens value or an error if the edge +// was not loaded in eager-loading. +func (e UserEdges) ActionTokensOrErr() ([]*ActionToken, error) { + if e.loadedTypes[3] { + return e.ActionTokens, nil + } + return nil, &NotLoadedError{edge: "action_tokens"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*User) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -213,6 +224,11 @@ func (u *User) QueryNotifiers() *NotifierQuery { return NewUserClient(u.config).QueryNotifiers(u) } +// QueryActionTokens queries the "action_tokens" edge of the User entity. +func (u *User) QueryActionTokens() *ActionTokenQuery { + return NewUserClient(u.config).QueryActionTokens(u) +} + // Update returns a builder for updating this User. // Note that you need to call User.Unwrap() before calling this method if this User // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/backend/internal/data/ent/user/user.go b/backend/internal/data/ent/user/user.go index 33b657bd..79f10197 100644 --- a/backend/internal/data/ent/user/user.go +++ b/backend/internal/data/ent/user/user.go @@ -40,6 +40,8 @@ const ( EdgeAuthTokens = "auth_tokens" // EdgeNotifiers holds the string denoting the notifiers edge name in mutations. EdgeNotifiers = "notifiers" + // EdgeActionTokens holds the string denoting the action_tokens edge name in mutations. + EdgeActionTokens = "action_tokens" // Table holds the table name of the user in the database. Table = "users" // GroupTable is the table that holds the group relation/edge. @@ -63,6 +65,13 @@ const ( NotifiersInverseTable = "notifiers" // NotifiersColumn is the table column denoting the notifiers relation/edge. NotifiersColumn = "user_id" + // ActionTokensTable is the table that holds the action_tokens relation/edge. + ActionTokensTable = "action_tokens" + // ActionTokensInverseTable is the table name for the ActionToken entity. + // It exists in this package in order to avoid circular dependency with the "actiontoken" package. + ActionTokensInverseTable = "action_tokens" + // ActionTokensColumn is the table column denoting the action_tokens relation/edge. + ActionTokensColumn = "user_id" ) // Columns holds all SQL columns for user fields. @@ -234,6 +243,20 @@ func ByNotifiers(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { sqlgraph.OrderByNeighborTerms(s, newNotifiersStep(), append([]sql.OrderTerm{term}, terms...)...) } } + +// ByActionTokensCount orders the results by action_tokens count. +func ByActionTokensCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newActionTokensStep(), opts...) + } +} + +// ByActionTokens orders the results by action_tokens terms. +func ByActionTokens(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newActionTokensStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} func newGroupStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), @@ -255,3 +278,10 @@ func newNotifiersStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.O2M, false, NotifiersTable, NotifiersColumn), ) } +func newActionTokensStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(ActionTokensInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ActionTokensTable, ActionTokensColumn), + ) +} diff --git a/backend/internal/data/ent/user/where.go b/backend/internal/data/ent/user/where.go index 8686e737..a4859545 100644 --- a/backend/internal/data/ent/user/where.go +++ b/backend/internal/data/ent/user/where.go @@ -530,6 +530,29 @@ func HasNotifiersWith(preds ...predicate.Notifier) predicate.User { }) } +// HasActionTokens applies the HasEdge predicate on the "action_tokens" edge. +func HasActionTokens() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, ActionTokensTable, ActionTokensColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasActionTokensWith applies the HasEdge predicate on the "action_tokens" edge with a given conditions (other predicates). +func HasActionTokensWith(preds ...predicate.ActionToken) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newActionTokensStep() + 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.User) predicate.User { return predicate.User(sql.AndPredicates(predicates...)) diff --git a/backend/internal/data/ent/user_create.go b/backend/internal/data/ent/user_create.go index 2cfe2d18..74534ba7 100644 --- a/backend/internal/data/ent/user_create.go +++ b/backend/internal/data/ent/user_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/actiontoken" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/notifier" @@ -181,6 +182,21 @@ func (uc *UserCreate) AddNotifiers(n ...*Notifier) *UserCreate { return uc.AddNotifierIDs(ids...) } +// AddActionTokenIDs adds the "action_tokens" edge to the ActionToken entity by IDs. +func (uc *UserCreate) AddActionTokenIDs(ids ...uuid.UUID) *UserCreate { + uc.mutation.AddActionTokenIDs(ids...) + return uc +} + +// AddActionTokens adds the "action_tokens" edges to the ActionToken entity. +func (uc *UserCreate) AddActionTokens(a ...*ActionToken) *UserCreate { + ids := make([]uuid.UUID, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return uc.AddActionTokenIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (uc *UserCreate) Mutation() *UserMutation { return uc.mutation @@ -411,6 +427,22 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { } _spec.Edges = append(_spec.Edges, edge) } + if nodes := uc.mutation.ActionTokensIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ActionTokensTable, + Columns: []string{user.ActionTokensColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID), + }, + } + 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/user_query.go b/backend/internal/data/ent/user_query.go index 7205e9b1..1abe0e2e 100644 --- a/backend/internal/data/ent/user_query.go +++ b/backend/internal/data/ent/user_query.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/actiontoken" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/notifier" @@ -22,14 +23,15 @@ import ( // UserQuery is the builder for querying User entities. type UserQuery struct { config - ctx *QueryContext - order []user.OrderOption - inters []Interceptor - predicates []predicate.User - withGroup *GroupQuery - withAuthTokens *AuthTokensQuery - withNotifiers *NotifierQuery - withFKs bool + ctx *QueryContext + order []user.OrderOption + inters []Interceptor + predicates []predicate.User + withGroup *GroupQuery + withAuthTokens *AuthTokensQuery + withNotifiers *NotifierQuery + withActionTokens *ActionTokenQuery + withFKs bool // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -132,6 +134,28 @@ func (uq *UserQuery) QueryNotifiers() *NotifierQuery { return query } +// QueryActionTokens chains the current query on the "action_tokens" edge. +func (uq *UserQuery) QueryActionTokens() *ActionTokenQuery { + query := (&ActionTokenClient{config: uq.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := uq.prepareQuery(ctx); err != nil { + return nil, err + } + selector := uq.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(actiontoken.Table, actiontoken.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, user.ActionTokensTable, user.ActionTokensColumn), + ) + fromU = sqlgraph.SetNeighbors(uq.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first User entity from the query. // Returns a *NotFoundError when no User was found. func (uq *UserQuery) First(ctx context.Context) (*User, error) { @@ -319,14 +343,15 @@ func (uq *UserQuery) Clone() *UserQuery { return nil } return &UserQuery{ - config: uq.config, - ctx: uq.ctx.Clone(), - order: append([]user.OrderOption{}, uq.order...), - inters: append([]Interceptor{}, uq.inters...), - predicates: append([]predicate.User{}, uq.predicates...), - withGroup: uq.withGroup.Clone(), - withAuthTokens: uq.withAuthTokens.Clone(), - withNotifiers: uq.withNotifiers.Clone(), + config: uq.config, + ctx: uq.ctx.Clone(), + order: append([]user.OrderOption{}, uq.order...), + inters: append([]Interceptor{}, uq.inters...), + predicates: append([]predicate.User{}, uq.predicates...), + withGroup: uq.withGroup.Clone(), + withAuthTokens: uq.withAuthTokens.Clone(), + withNotifiers: uq.withNotifiers.Clone(), + withActionTokens: uq.withActionTokens.Clone(), // clone intermediate query. sql: uq.sql.Clone(), path: uq.path, @@ -366,6 +391,17 @@ func (uq *UserQuery) WithNotifiers(opts ...func(*NotifierQuery)) *UserQuery { return uq } +// WithActionTokens tells the query-builder to eager-load the nodes that are connected to +// the "action_tokens" edge. The optional arguments are used to configure the query builder of the edge. +func (uq *UserQuery) WithActionTokens(opts ...func(*ActionTokenQuery)) *UserQuery { + query := (&ActionTokenClient{config: uq.config}).Query() + for _, opt := range opts { + opt(query) + } + uq.withActionTokens = query + return uq +} + // 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. // @@ -445,10 +481,11 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e nodes = []*User{} withFKs = uq.withFKs _spec = uq.querySpec() - loadedTypes = [3]bool{ + loadedTypes = [4]bool{ uq.withGroup != nil, uq.withAuthTokens != nil, uq.withNotifiers != nil, + uq.withActionTokens != nil, } ) if uq.withGroup != nil { @@ -495,6 +532,13 @@ func (uq *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e return nil, err } } + if query := uq.withActionTokens; query != nil { + if err := uq.loadActionTokens(ctx, query, nodes, + func(n *User) { n.Edges.ActionTokens = []*ActionToken{} }, + func(n *User, e *ActionToken) { n.Edges.ActionTokens = append(n.Edges.ActionTokens, e) }); err != nil { + return nil, err + } + } return nodes, nil } @@ -591,6 +635,36 @@ func (uq *UserQuery) loadNotifiers(ctx context.Context, query *NotifierQuery, no } return nil } +func (uq *UserQuery) loadActionTokens(ctx context.Context, query *ActionTokenQuery, nodes []*User, init func(*User), assign func(*User, *ActionToken)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[uuid.UUID]*User) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(actiontoken.FieldUserID) + } + query.Where(predicate.ActionToken(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(user.ActionTokensColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.UserID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} func (uq *UserQuery) sqlCount(ctx context.Context) (int, error) { _spec := uq.querySpec() diff --git a/backend/internal/data/ent/user_update.go b/backend/internal/data/ent/user_update.go index 0e4c01ac..a9435251 100644 --- a/backend/internal/data/ent/user_update.go +++ b/backend/internal/data/ent/user_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/actiontoken" "github.com/hay-kot/homebox/backend/internal/data/ent/authtokens" "github.com/hay-kot/homebox/backend/internal/data/ent/group" "github.com/hay-kot/homebox/backend/internal/data/ent/notifier" @@ -183,6 +184,21 @@ func (uu *UserUpdate) AddNotifiers(n ...*Notifier) *UserUpdate { return uu.AddNotifierIDs(ids...) } +// AddActionTokenIDs adds the "action_tokens" edge to the ActionToken entity by IDs. +func (uu *UserUpdate) AddActionTokenIDs(ids ...uuid.UUID) *UserUpdate { + uu.mutation.AddActionTokenIDs(ids...) + return uu +} + +// AddActionTokens adds the "action_tokens" edges to the ActionToken entity. +func (uu *UserUpdate) AddActionTokens(a ...*ActionToken) *UserUpdate { + ids := make([]uuid.UUID, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return uu.AddActionTokenIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (uu *UserUpdate) Mutation() *UserMutation { return uu.mutation @@ -236,6 +252,27 @@ func (uu *UserUpdate) RemoveNotifiers(n ...*Notifier) *UserUpdate { return uu.RemoveNotifierIDs(ids...) } +// ClearActionTokens clears all "action_tokens" edges to the ActionToken entity. +func (uu *UserUpdate) ClearActionTokens() *UserUpdate { + uu.mutation.ClearActionTokens() + return uu +} + +// RemoveActionTokenIDs removes the "action_tokens" edge to ActionToken entities by IDs. +func (uu *UserUpdate) RemoveActionTokenIDs(ids ...uuid.UUID) *UserUpdate { + uu.mutation.RemoveActionTokenIDs(ids...) + return uu +} + +// RemoveActionTokens removes "action_tokens" edges to ActionToken entities. +func (uu *UserUpdate) RemoveActionTokens(a ...*ActionToken) *UserUpdate { + ids := make([]uuid.UUID, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return uu.RemoveActionTokenIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (uu *UserUpdate) Save(ctx context.Context) (int, error) { uu.defaults() @@ -458,6 +495,51 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if uu.mutation.ActionTokensCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ActionTokensTable, + Columns: []string{user.ActionTokensColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.RemovedActionTokensIDs(); len(nodes) > 0 && !uu.mutation.ActionTokensCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ActionTokensTable, + Columns: []string{user.ActionTokensColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uu.mutation.ActionTokensIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ActionTokensTable, + Columns: []string{user.ActionTokensColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID), + }, + } + 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, uu.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{user.Label} @@ -629,6 +711,21 @@ func (uuo *UserUpdateOne) AddNotifiers(n ...*Notifier) *UserUpdateOne { return uuo.AddNotifierIDs(ids...) } +// AddActionTokenIDs adds the "action_tokens" edge to the ActionToken entity by IDs. +func (uuo *UserUpdateOne) AddActionTokenIDs(ids ...uuid.UUID) *UserUpdateOne { + uuo.mutation.AddActionTokenIDs(ids...) + return uuo +} + +// AddActionTokens adds the "action_tokens" edges to the ActionToken entity. +func (uuo *UserUpdateOne) AddActionTokens(a ...*ActionToken) *UserUpdateOne { + ids := make([]uuid.UUID, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return uuo.AddActionTokenIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (uuo *UserUpdateOne) Mutation() *UserMutation { return uuo.mutation @@ -682,6 +779,27 @@ func (uuo *UserUpdateOne) RemoveNotifiers(n ...*Notifier) *UserUpdateOne { return uuo.RemoveNotifierIDs(ids...) } +// ClearActionTokens clears all "action_tokens" edges to the ActionToken entity. +func (uuo *UserUpdateOne) ClearActionTokens() *UserUpdateOne { + uuo.mutation.ClearActionTokens() + return uuo +} + +// RemoveActionTokenIDs removes the "action_tokens" edge to ActionToken entities by IDs. +func (uuo *UserUpdateOne) RemoveActionTokenIDs(ids ...uuid.UUID) *UserUpdateOne { + uuo.mutation.RemoveActionTokenIDs(ids...) + return uuo +} + +// RemoveActionTokens removes "action_tokens" edges to ActionToken entities. +func (uuo *UserUpdateOne) RemoveActionTokens(a ...*ActionToken) *UserUpdateOne { + ids := make([]uuid.UUID, len(a)) + for i := range a { + ids[i] = a[i].ID + } + return uuo.RemoveActionTokenIDs(ids...) +} + // Where appends a list predicates to the UserUpdate builder. func (uuo *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne { uuo.mutation.Where(ps...) @@ -934,6 +1052,51 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if uuo.mutation.ActionTokensCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ActionTokensTable, + Columns: []string{user.ActionTokensColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.RemovedActionTokensIDs(); len(nodes) > 0 && !uuo.mutation.ActionTokensCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ActionTokensTable, + Columns: []string{user.ActionTokensColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := uuo.mutation.ActionTokensIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: user.ActionTokensTable, + Columns: []string{user.ActionTokensColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(actiontoken.FieldID, field.TypeUUID), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &User{config: uuo.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/backend/internal/data/migrations/migrations/20240302172225_user_action_tokens.sql b/backend/internal/data/migrations/migrations/20240302172225_user_action_tokens.sql new file mode 100644 index 00000000..384b1ec8 --- /dev/null +++ b/backend/internal/data/migrations/migrations/20240302172225_user_action_tokens.sql @@ -0,0 +1,10 @@ +-- Create "action_tokens" table +CREATE TABLE `action_tokens` (`id` uuid NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `action` text NOT NULL DEFAULT ('reset_password'), `token` blob NOT NULL, `user_id` uuid NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `action_tokens_users_action_tokens` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE); +-- Create index "action_tokens_token_key" to table: "action_tokens" +CREATE UNIQUE INDEX `action_tokens_token_key` ON `action_tokens` (`token`); +-- Create index "actiontoken_token" to table: "action_tokens" +CREATE INDEX `actiontoken_token` ON `action_tokens` (`token`); +-- Create index "actiontoken_action" to table: "action_tokens" +CREATE INDEX `actiontoken_action` ON `action_tokens` (`action`); +-- Create index "actiontoken_user_id" to table: "action_tokens" +CREATE INDEX `actiontoken_user_id` ON `action_tokens` (`user_id`); diff --git a/backend/internal/data/migrations/migrations/atlas.sum b/backend/internal/data/migrations/migrations/atlas.sum index e8d99a61..3006d543 100644 --- a/backend/internal/data/migrations/migrations/atlas.sum +++ b/backend/internal/data/migrations/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:sjJCTAqc9FG8BKBIzh5ZynYD/Ilz6vnLqM4XX83WQ4M= +h1:6CJ6qlt5IqAoKF6R9yH8Ei2eqkAbkCqjM1dDUvA24f8= 20220929052825_init.sql h1:ZlCqm1wzjDmofeAcSX3jE4h4VcdTNGpRg2eabztDy9Q= 20221001210956_group_invitations.sql h1:YQKJFtE39wFOcRNbZQ/d+ZlHwrcfcsZlcv/pLEYdpjw= 20221009173029_add_user_roles.sql h1:vWmzAfgEWQeGk0Vn70zfVPCcfEZth3E0JcvyKTjpYyU= @@ -13,3 +13,4 @@ h1:sjJCTAqc9FG8BKBIzh5ZynYD/Ilz6vnLqM4XX83WQ4M= 20230305065819_add_notifier_types.sql h1:r5xrgCKYQ2o9byBqYeAX1zdp94BLdaxf4vq9OmGHNl0= 20230305071524_add_group_id_to_notifiers.sql h1:xDShqbyClcFhvJbwclOHdczgXbdffkxXNWjV61hL/t4= 20231006213457_add_primary_attachment_flag.sql h1:J4tMSJQFa7vaj0jpnh8YKTssdyIjRyq6RXDXZIzDDu4= +20240302172225_user_action_tokens.sql h1:nAdtVdh9O7p/AyKnURvZcS0H/Zoj1sM9HpwCOO09zDE= diff --git a/backend/internal/data/repo/asset_id_type.go b/backend/internal/data/repo/asset_id_type.go index 0a53a4a1..5c35a04b 100644 --- a/backend/internal/data/repo/asset_id_type.go +++ b/backend/internal/data/repo/asset_id_type.go @@ -16,9 +16,9 @@ func (aid AssetID) Int() int { return int(aid) } -func ParseAssetIDBytes(d []byte) (AID AssetID, ok bool) { - d = bytes.Replace(d, []byte(`"`), []byte(``), -1) - d = bytes.Replace(d, []byte(`-`), []byte(``), -1) +func ParseAssetIDBytes(d []byte) (assetID AssetID, ok bool) { + d = bytes.ReplaceAll(d, []byte(`"`), []byte(``)) + d = bytes.ReplaceAll(d, []byte(`-`), []byte(``)) aidInt, err := strconv.Atoi(string(d)) if err != nil { @@ -28,7 +28,7 @@ func ParseAssetIDBytes(d []byte) (AID AssetID, ok bool) { return AssetID(aidInt), true } -func ParseAssetID(s string) (AID AssetID, ok bool) { +func ParseAssetID(s string) (assetID AssetID, ok bool) { return ParseAssetIDBytes([]byte(s)) } @@ -52,8 +52,8 @@ func (aid *AssetID) UnmarshalJSON(d []byte) error { return nil } - d = bytes.Replace(d, []byte(`"`), []byte(``), -1) - d = bytes.Replace(d, []byte(`-`), []byte(``), -1) + d = bytes.ReplaceAll(d, []byte(`"`), []byte(``)) + d = bytes.ReplaceAll(d, []byte(`-`), []byte(``)) aidInt, err := strconv.Atoi(string(d)) if err != nil { diff --git a/backend/internal/data/repo/main_test.go b/backend/internal/data/repo/main_test.go index 47e5ec05..50512453 100644 --- a/backend/internal/data/repo/main_test.go +++ b/backend/internal/data/repo/main_test.go @@ -56,9 +56,10 @@ func TestMain(m *testing.M) { tClient = client tRepos = New(tClient, tbus, os.TempDir()) - defer func() { _ = client.Close() }() bootstrap() - os.Exit(m.Run()) + exit := m.Run() + _ = client.Close() + os.Exit(exit) } diff --git a/backend/internal/data/repo/repo_group.go b/backend/internal/data/repo/repo_group.go index 8f93c780..f02c2114 100644 --- a/backend/internal/data/repo/repo_group.go +++ b/backend/internal/data/repo/repo_group.go @@ -109,12 +109,12 @@ func (r *GroupRepository) GetAllGroups(ctx context.Context) ([]Group, error) { return r.groupMapper.MapEachErr(r.db.Group.Query().All(ctx)) } -func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, GID uuid.UUID) ([]TotalsByOrganizer, error) { +func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, groupID uuid.UUID) ([]TotalsByOrganizer, error) { var v []TotalsByOrganizer err := r.db.Location.Query(). Where( - location.HasGroupWith(group.ID(GID)), + location.HasGroupWith(group.ID(groupID)), ). GroupBy(location.FieldID, location.FieldName). Aggregate(func(sq *sql.Selector) string { @@ -131,12 +131,12 @@ func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, GID return v, err } -func (r *GroupRepository) StatsLabelsByPurchasePrice(ctx context.Context, GID uuid.UUID) ([]TotalsByOrganizer, error) { +func (r *GroupRepository) StatsLabelsByPurchasePrice(ctx context.Context, groupID uuid.UUID) ([]TotalsByOrganizer, error) { var v []TotalsByOrganizer err := r.db.Label.Query(). Where( - label.HasGroupWith(group.ID(GID)), + label.HasGroupWith(group.ID(groupID)), ). GroupBy(label.FieldID, label.FieldName). Aggregate(func(sq *sql.Selector) string { @@ -157,7 +157,7 @@ func (r *GroupRepository) StatsLabelsByPurchasePrice(ctx context.Context, GID uu return v, err } -func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID, start, end time.Time) (*ValueOverTime, error) { +func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, groupID uuid.UUID, start, end time.Time) (*ValueOverTime, error) { // Get the Totals for the Start and End of the Given Time Period q := ` SELECT @@ -180,7 +180,7 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID, var maybeStart *float64 var maybeEnd *float64 - row := r.db.Sql().QueryRowContext(ctx, q, GID, sqliteDateFormat(start), GID, sqliteDateFormat(end)) + row := r.db.Sql().QueryRowContext(ctx, q, groupID, sqliteDateFormat(start), groupID, sqliteDateFormat(end)) err := row.Scan(&maybeStart, &maybeEnd) if err != nil { return nil, err @@ -198,7 +198,7 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID, // Get Created Date and Price of all items between start and end err = r.db.Item.Query(). Where( - item.HasGroupWith(group.ID(GID)), + item.HasGroupWith(group.ID(groupID)), item.CreatedAtGTE(start), item.CreatedAtLTE(end), item.Archived(false), @@ -209,7 +209,6 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID, item.FieldPurchasePrice, ). Scan(ctx, &v) - if err != nil { return nil, err } @@ -226,7 +225,7 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID, return &stats, nil } -func (r *GroupRepository) StatsGroup(ctx context.Context, GID uuid.UUID) (GroupStatistics, error) { +func (r *GroupRepository) StatsGroup(ctx context.Context, groupID uuid.UUID) (GroupStatistics, error) { q := ` SELECT (SELECT COUNT(*) FROM users WHERE group_users = ?) AS total_users, @@ -242,7 +241,7 @@ func (r *GroupRepository) StatsGroup(ctx context.Context, GID uuid.UUID) (GroupS ) AS total_with_warranty ` var stats GroupStatistics - row := r.db.Sql().QueryRowContext(ctx, q, GID, GID, GID, GID, GID, GID) + row := r.db.Sql().QueryRowContext(ctx, q, groupID, groupID, groupID, groupID, groupID, groupID) var maybeTotalItemPrice *float64 var maybeTotalWithWarranty *int @@ -264,8 +263,8 @@ func (r *GroupRepository) GroupCreate(ctx context.Context, name string) (Group, Save(ctx)) } -func (r *GroupRepository) GroupUpdate(ctx context.Context, ID uuid.UUID, data GroupUpdate) (Group, error) { - entity, err := r.db.Group.UpdateOneID(ID). +func (r *GroupRepository) GroupUpdate(ctx context.Context, groupID uuid.UUID, data GroupUpdate) (Group, error) { + entity, err := r.db.Group.UpdateOneID(groupID). SetName(data.Name). SetCurrency(strings.ToLower(data.Currency)). Save(ctx) @@ -273,8 +272,8 @@ func (r *GroupRepository) GroupUpdate(ctx context.Context, ID uuid.UUID, data Gr return r.groupMapper.MapErr(entity, err) } -func (r *GroupRepository) GroupByID(ctx context.Context, id uuid.UUID) (Group, error) { - return r.groupMapper.MapErr(r.db.Group.Get(ctx, id)) +func (r *GroupRepository) GroupByID(ctx context.Context, groupID uuid.UUID) (Group, error) { + return r.groupMapper.MapErr(r.db.Group.Get(ctx, groupID)) } func (r *GroupRepository) InvitationGet(ctx context.Context, token []byte) (GroupInvitation, error) { diff --git a/backend/internal/data/repo/repo_items.go b/backend/internal/data/repo/repo_items.go index 72ba9040..b1c73a04 100644 --- a/backend/internal/data/repo/repo_items.go +++ b/backend/internal/data/repo/repo_items.go @@ -276,9 +276,9 @@ func mapItemOut(item *ent.Item) ItemOut { } } -func (e *ItemsRepository) publishMutationEvent(GID uuid.UUID) { +func (e *ItemsRepository) publishMutationEvent(groupID uuid.UUID) { if e.bus != nil { - e.bus.Publish(eventbus.EventItemMutation, eventbus.GroupMutationEvent{GID: GID}) + e.bus.Publish(eventbus.EventItemMutation, eventbus.GroupMutationEvent{GID: groupID}) } } @@ -304,13 +304,13 @@ func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (ItemOut, er return e.getOne(ctx, item.ID(id)) } -func (e *ItemsRepository) CheckRef(ctx context.Context, GID uuid.UUID, ref string) (bool, error) { - q := e.db.Item.Query().Where(item.HasGroupWith(group.ID(GID))) +func (e *ItemsRepository) CheckRef(ctx context.Context, groupID uuid.UUID, ref string) (bool, error) { + q := e.db.Item.Query().Where(item.HasGroupWith(group.ID(groupID))) return q.Where(item.ImportRef(ref)).Exist(ctx) } -func (e *ItemsRepository) GetByRef(ctx context.Context, GID uuid.UUID, ref string) (ItemOut, error) { - return e.getOne(ctx, item.ImportRef(ref), item.HasGroupWith(group.ID(GID))) +func (e *ItemsRepository) GetByRef(ctx context.Context, groupID uuid.UUID, ref string) (ItemOut, error) { + return e.getOne(ctx, item.ImportRef(ref), item.HasGroupWith(group.ID(groupID))) } // GetOneByGroup returns a single item by ID. If the item does not exist, an error is returned. @@ -490,9 +490,9 @@ func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemOut, All(ctx)) } -func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, GID uuid.UUID) ([]ItemSummary, error) { +func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, groupID uuid.UUID) ([]ItemSummary, error) { q := e.db.Item.Query().Where( - item.HasGroupWith(group.ID(GID)), + item.HasGroupWith(group.ID(groupID)), item.AssetID(0), ).Order( ent.Asc(item.FieldCreatedAt), @@ -501,9 +501,9 @@ func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, GID uuid.UUID) return mapItemsSummaryErr(q.All(ctx)) } -func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, GID uuid.UUID) (AssetID, error) { +func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, groupID uuid.UUID) (AssetID, error) { q := e.db.Item.Query().Where( - item.HasGroupWith(group.ID(GID)), + item.HasGroupWith(group.ID(groupID)), ).Order( ent.Desc(item.FieldAssetID), ).Limit(1) @@ -519,10 +519,10 @@ func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, GID uuid.UUID) return AssetID(result.AssetID), nil } -func (e *ItemsRepository) SetAssetID(ctx context.Context, GID uuid.UUID, ID uuid.UUID, assetID AssetID) error { +func (e *ItemsRepository) SetAssetID(ctx context.Context, groupID uuid.UUID, itemID uuid.UUID, assetID AssetID) error { q := e.db.Item.Update().Where( - item.HasGroupWith(group.ID(GID)), - item.ID(ID), + item.HasGroupWith(group.ID(groupID)), + item.ID(itemID), ) _, err := q.SetAssetID(int(assetID)).Save(ctx) @@ -576,8 +576,8 @@ func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID) return err } -func (e *ItemsRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data ItemUpdate) (ItemOut, error) { - q := e.db.Item.Update().Where(item.ID(data.ID), item.HasGroupWith(group.ID(GID))). +func (e *ItemsRepository) UpdateByGroup(ctx context.Context, groupID uuid.UUID, data ItemUpdate) (ItemOut, error) { + q := e.db.Item.Update().Where(item.ID(data.ID), item.HasGroupWith(group.ID(groupID))). SetName(data.Name). SetDescription(data.Description). SetLocationID(data.LocationID). @@ -688,16 +688,16 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data } } - e.publishMutationEvent(GID) + e.publishMutationEvent(groupID) return e.GetOne(ctx, data.ID) } -func (e *ItemsRepository) GetAllZeroImportRef(ctx context.Context, GID uuid.UUID) ([]uuid.UUID, error) { +func (e *ItemsRepository) GetAllZeroImportRef(ctx context.Context, groupID uuid.UUID) ([]uuid.UUID, error) { var ids []uuid.UUID err := e.db.Item.Query(). Where( - item.HasGroupWith(group.ID(GID)), + item.HasGroupWith(group.ID(groupID)), item.Or( item.ImportRefEQ(""), item.ImportRefIsNil(), @@ -712,11 +712,11 @@ func (e *ItemsRepository) GetAllZeroImportRef(ctx context.Context, GID uuid.UUID return ids, nil } -func (e *ItemsRepository) Patch(ctx context.Context, GID, ID uuid.UUID, data ItemPatch) error { +func (e *ItemsRepository) Patch(ctx context.Context, groupID, itemID uuid.UUID, data ItemPatch) error { q := e.db.Item.Update(). Where( - item.ID(ID), - item.HasGroupWith(group.ID(GID)), + item.ID(itemID), + item.HasGroupWith(group.ID(groupID)), ) if data.ImportRef != nil { @@ -727,11 +727,11 @@ func (e *ItemsRepository) Patch(ctx context.Context, GID, ID uuid.UUID, data Ite q.SetQuantity(*data.Quantity) } - e.publishMutationEvent(GID) + e.publishMutationEvent(groupID) return q.Exec(ctx) } -func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, GID uuid.UUID, name string) ([]string, error) { +func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, groupID uuid.UUID, name string) ([]string, error) { type st struct { Value string `json:"text_value"` } @@ -740,7 +740,7 @@ func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, GID uuid. err := e.db.Item.Query(). Where( - item.HasGroupWith(group.ID(GID)), + item.HasGroupWith(group.ID(groupID)), ). QueryFields(). Where( @@ -761,7 +761,7 @@ func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, GID uuid. return valueStrings, nil } -func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, GID uuid.UUID) ([]string, error) { +func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, groupID uuid.UUID) ([]string, error) { type st struct { Name string `json:"name"` } @@ -770,7 +770,7 @@ func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, GID uuid.U err := e.db.Item.Query(). Where( - item.HasGroupWith(group.ID(GID)), + item.HasGroupWith(group.ID(groupID)), ). QueryFields(). Unique(true). @@ -794,9 +794,9 @@ func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, GID uuid.U // This is designed to resolve a long-time bug that has since been fixed with the time selector on the // frontend. This function is intended to be used as a one-time fix for existing databases and may be // removed in the future. -func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID) (int, error) { +func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, groupID uuid.UUID) (int, error) { q := e.db.Item.Query().Where( - item.HasGroupWith(group.ID(GID)), + item.HasGroupWith(group.ID(groupID)), item.Or( item.PurchaseTimeNotNil(), item.PurchaseFromLT("0002-01-01"), @@ -865,11 +865,11 @@ func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID) return updated, nil } -func (e *ItemsRepository) SetPrimaryPhotos(ctx context.Context, GID uuid.UUID) (int, error) { +func (e *ItemsRepository) SetPrimaryPhotos(ctx context.Context, groupID uuid.UUID) (int, error) { // All items where there is no primary photo itemIDs, err := e.db.Item.Query(). Where( - item.HasGroupWith(group.ID(GID)), + item.HasGroupWith(group.ID(groupID)), item.HasAttachmentsWith( attachment.TypeEQ(attachment.TypePhoto), attachment.Not( diff --git a/backend/internal/data/repo/repo_labels.go b/backend/internal/data/repo/repo_labels.go index 2358f9c6..d27d276a 100644 --- a/backend/internal/data/repo/repo_labels.go +++ b/backend/internal/data/repo/repo_labels.go @@ -65,9 +65,9 @@ func mapLabelOut(label *ent.Label) LabelOut { } } -func (r *LabelRepository) publishMutationEvent(GID uuid.UUID) { +func (r *LabelRepository) publishMutationEvent(groupID uuid.UUID) { if r.bus != nil { - r.bus.Publish(eventbus.EventLabelMutation, eventbus.GroupMutationEvent{GID: GID}) + r.bus.Publish(eventbus.EventLabelMutation, eventbus.GroupMutationEvent{GID: groupID}) } } @@ -79,8 +79,8 @@ func (r *LabelRepository) getOne(ctx context.Context, where ...predicate.Label) ) } -func (r *LabelRepository) GetOne(ctx context.Context, ID uuid.UUID) (LabelOut, error) { - return r.getOne(ctx, label.ID(ID)) +func (r *LabelRepository) GetOne(ctx context.Context, labelID uuid.UUID) (LabelOut, error) { + return r.getOne(ctx, label.ID(labelID)) } func (r *LabelRepository) GetOneByGroup(ctx context.Context, gid, ld uuid.UUID) (LabelOut, error) { @@ -125,13 +125,13 @@ func (r *LabelRepository) update(ctx context.Context, data LabelUpdate, where .. Save(ctx) } -func (r *LabelRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data LabelUpdate) (LabelOut, error) { - _, err := r.update(ctx, data, label.ID(data.ID), label.HasGroupWith(group.ID(GID))) +func (r *LabelRepository) UpdateByGroup(ctx context.Context, groupID uuid.UUID, data LabelUpdate) (LabelOut, error) { + _, err := r.update(ctx, data, label.ID(data.ID), label.HasGroupWith(group.ID(groupID))) if err != nil { return LabelOut{}, err } - r.publishMutationEvent(GID) + r.publishMutationEvent(groupID) return r.GetOne(ctx, data.ID) } diff --git a/backend/internal/data/repo/repo_locations.go b/backend/internal/data/repo/repo_locations.go index fd98fd78..76987765 100644 --- a/backend/internal/data/repo/repo_locations.go +++ b/backend/internal/data/repo/repo_locations.go @@ -89,9 +89,9 @@ func mapLocationOut(location *ent.Location) LocationOut { } } -func (r *LocationRepository) publishMutationEvent(GID uuid.UUID) { +func (r *LocationRepository) publishMutationEvent(groupID uuid.UUID) { if r.bus != nil { - r.bus.Publish(eventbus.EventLocationMutation, eventbus.GroupMutationEvent{GID: GID}) + r.bus.Publish(eventbus.EventLocationMutation, eventbus.GroupMutationEvent{GID: groupID}) } } @@ -100,7 +100,7 @@ type LocationQuery struct { } // GetAll returns all locations with item count field populated -func (r *LocationRepository) GetAll(ctx context.Context, GID uuid.UUID, filter LocationQuery) ([]LocationOutCount, error) { +func (r *LocationRepository) GetAll(ctx context.Context, groupID uuid.UUID, filter LocationQuery) ([]LocationOutCount, error) { query := `--sql SELECT id, @@ -131,7 +131,7 @@ func (r *LocationRepository) GetAll(ctx context.Context, GID uuid.UUID, filter L query = strings.Replace(query, "{{ FILTER_CHILDREN }}", "", 1) } - rows, err := r.db.Sql().QueryContext(ctx, query, GID) + rows, err := r.db.Sql().QueryContext(ctx, query, groupID) if err != nil { return nil, err } @@ -167,19 +167,19 @@ func (r *LocationRepository) getOne(ctx context.Context, where ...predicate.Loca Only(ctx)) } -func (r *LocationRepository) Get(ctx context.Context, ID uuid.UUID) (LocationOut, error) { - return r.getOne(ctx, location.ID(ID)) +func (r *LocationRepository) Get(ctx context.Context, locationID uuid.UUID) (LocationOut, error) { + return r.getOne(ctx, location.ID(locationID)) } -func (r *LocationRepository) GetOneByGroup(ctx context.Context, GID, ID uuid.UUID) (LocationOut, error) { - return r.getOne(ctx, location.ID(ID), location.HasGroupWith(group.ID(GID))) +func (r *LocationRepository) GetOneByGroup(ctx context.Context, groupID, locationID uuid.UUID) (LocationOut, error) { + return r.getOne(ctx, location.ID(locationID), location.HasGroupWith(group.ID(groupID))) } -func (r *LocationRepository) Create(ctx context.Context, GID uuid.UUID, data LocationCreate) (LocationOut, error) { +func (r *LocationRepository) Create(ctx context.Context, groupID uuid.UUID, data LocationCreate) (LocationOut, error) { q := r.db.Location.Create(). SetName(data.Name). SetDescription(data.Description). - SetGroupID(GID) + SetGroupID(groupID) if data.ParentID != uuid.Nil { q.SetParentID(data.ParentID) @@ -190,8 +190,8 @@ func (r *LocationRepository) Create(ctx context.Context, GID uuid.UUID, data Loc return LocationOut{}, err } - location.Edges.Group = &ent.Group{ID: GID} // bootstrap group ID - r.publishMutationEvent(GID) + location.Edges.Group = &ent.Group{ID: groupID} // bootstrap group ID + r.publishMutationEvent(groupID) return mapLocationOut(location), nil } @@ -215,28 +215,28 @@ func (r *LocationRepository) update(ctx context.Context, data LocationUpdate, wh return r.Get(ctx, data.ID) } -func (r *LocationRepository) UpdateByGroup(ctx context.Context, GID, ID uuid.UUID, data LocationUpdate) (LocationOut, error) { - v, err := r.update(ctx, data, location.ID(ID), location.HasGroupWith(group.ID(GID))) +func (r *LocationRepository) UpdateByGroup(ctx context.Context, groupID, locationID uuid.UUID, data LocationUpdate) (LocationOut, error) { + v, err := r.update(ctx, data, location.ID(locationID), location.HasGroupWith(group.ID(groupID))) if err != nil { return LocationOut{}, err } - r.publishMutationEvent(GID) + r.publishMutationEvent(groupID) return v, err } // delete should only be used after checking that the location is owned by the // group. Otherwise, use DeleteByGroup -func (r *LocationRepository) delete(ctx context.Context, ID uuid.UUID) error { - return r.db.Location.DeleteOneID(ID).Exec(ctx) +func (r *LocationRepository) delete(ctx context.Context, locationID uuid.UUID) error { + return r.db.Location.DeleteOneID(locationID).Exec(ctx) } -func (r *LocationRepository) DeleteByGroup(ctx context.Context, GID, ID uuid.UUID) error { - _, err := r.db.Location.Delete().Where(location.ID(ID), location.HasGroupWith(group.ID(GID))).Exec(ctx) +func (r *LocationRepository) DeleteByGroup(ctx context.Context, groupID, locationID uuid.UUID) error { + _, err := r.db.Location.Delete().Where(location.ID(locationID), location.HasGroupWith(group.ID(groupID))).Exec(ctx) if err != nil { return err } - r.publishMutationEvent(GID) + r.publishMutationEvent(groupID) return err } @@ -273,7 +273,7 @@ type ItemPath struct { Name string `json:"name"` } -func (r *LocationRepository) PathForLoc(ctx context.Context, GID, locID uuid.UUID) ([]ItemPath, error) { +func (r *LocationRepository) PathForLoc(ctx context.Context, groupID, locID uuid.UUID) ([]ItemPath, error) { query := `WITH RECURSIVE location_path AS ( SELECT id, name, location_children FROM locations @@ -290,7 +290,7 @@ func (r *LocationRepository) PathForLoc(ctx context.Context, GID, locID uuid.UUI SELECT id, name FROM location_path` - rows, err := r.db.Sql().QueryContext(ctx, query, locID, GID) + rows, err := r.db.Sql().QueryContext(ctx, query, locID, groupID) if err != nil { return nil, err } @@ -320,7 +320,7 @@ func (r *LocationRepository) PathForLoc(ctx context.Context, GID, locID uuid.UUI return locations, nil } -func (r *LocationRepository) Tree(ctx context.Context, GID uuid.UUID, tq TreeQuery) ([]TreeItem, error) { +func (r *LocationRepository) Tree(ctx context.Context, groupID uuid.UUID, tq TreeQuery) ([]TreeItem, error) { query := ` WITH recursive location_tree(id, NAME, parent_id, level, node_type) AS ( @@ -402,7 +402,7 @@ func (r *LocationRepository) Tree(ctx context.Context, GID uuid.UUID, tq TreeQue query = strings.ReplaceAll(query, "{{ WITH_ITEMS_FROM }}", "") } - rows, err := r.db.Sql().QueryContext(ctx, query, GID) + rows, err := r.db.Sql().QueryContext(ctx, query, groupID) if err != nil { return nil, err } diff --git a/backend/internal/data/repo/repo_maintenance_entry.go b/backend/internal/data/repo/repo_maintenance_entry.go index 2714bbdd..c33bfe07 100644 --- a/backend/internal/data/repo/repo_maintenance_entry.go +++ b/backend/internal/data/repo/repo_maintenance_entry.go @@ -84,11 +84,11 @@ func mapMaintenanceEntry(entry *ent.MaintenanceEntry) MaintenanceEntry { } } -func (r *MaintenanceEntryRepository) GetScheduled(ctx context.Context, GID uuid.UUID, dt types.Date) ([]MaintenanceEntry, error) { +func (r *MaintenanceEntryRepository) GetScheduled(ctx context.Context, groupID uuid.UUID, dt types.Date) ([]MaintenanceEntry, error) { entries, err := r.db.MaintenanceEntry.Query(). Where( maintenanceentry.HasItemWith( - item.HasGroupWith(group.ID(GID)), + item.HasGroupWith(group.ID(groupID)), ), maintenanceentry.ScheduledDate(dt.Time()), maintenanceentry.Or( @@ -97,7 +97,6 @@ func (r *MaintenanceEntryRepository) GetScheduled(ctx context.Context, GID uuid. ), ). All(ctx) - if err != nil { return nil, err } @@ -118,8 +117,8 @@ func (r *MaintenanceEntryRepository) Create(ctx context.Context, itemID uuid.UUI return mapMaintenanceEntryErr(item, err) } -func (r *MaintenanceEntryRepository) Update(ctx context.Context, ID uuid.UUID, input MaintenanceEntryUpdate) (MaintenanceEntry, error) { - item, err := r.db.MaintenanceEntry.UpdateOneID(ID). +func (r *MaintenanceEntryRepository) Update(ctx context.Context, entryID uuid.UUID, input MaintenanceEntryUpdate) (MaintenanceEntry, error) { + item, err := r.db.MaintenanceEntry.UpdateOneID(entryID). SetDate(input.CompletedDate.Time()). SetScheduledDate(input.ScheduledDate.Time()). SetName(input.Name). @@ -202,6 +201,6 @@ FROM return log, nil } -func (r *MaintenanceEntryRepository) Delete(ctx context.Context, ID uuid.UUID) error { - return r.db.MaintenanceEntry.DeleteOneID(ID).Exec(ctx) +func (r *MaintenanceEntryRepository) Delete(ctx context.Context, entryID uuid.UUID) error { + return r.db.MaintenanceEntry.DeleteOneID(entryID).Exec(ctx) } diff --git a/backend/internal/data/repo/repo_maintenance_entry_test.go b/backend/internal/data/repo/repo_maintenance_entry_test.go index 0fa288c2..fddf5d62 100644 --- a/backend/internal/data/repo/repo_maintenance_entry_test.go +++ b/backend/internal/data/repo/repo_maintenance_entry_test.go @@ -78,7 +78,7 @@ func TestMaintenanceEntryRepository_GetLog(t *testing.T) { } assert.InDelta(t, total, log.CostTotal, .001, "total cost should be equal to the sum of all entries") - assert.InDelta(t, total/2, log.CostAverage, 001, "average cost should be the average of the two months") + assert.InDelta(t, total/2, log.CostAverage, 0o01, "average cost should be the average of the two months") for _, entry := range log.Entries { err := tRepos.MaintEntry.Delete(context.Background(), entry.ID) diff --git a/backend/internal/data/repo/repo_notifier.go b/backend/internal/data/repo/repo_notifier.go index f31be4b4..5aecaf3c 100644 --- a/backend/internal/data/repo/repo_notifier.go +++ b/backend/internal/data/repo/repo_notifier.go @@ -114,7 +114,7 @@ func (r *NotifierRepository) Update(ctx context.Context, userID uuid.UUID, id uu return r.mapper.MapErr(notifier, err) } -func (r *NotifierRepository) Delete(ctx context.Context, userID uuid.UUID, ID uuid.UUID) error { - _, err := r.db.Notifier.Delete().Where(notifier.UserID(userID), notifier.ID(ID)).Exec(ctx) +func (r *NotifierRepository) Delete(ctx context.Context, userID uuid.UUID, notifierID uuid.UUID) error { + _, err := r.db.Notifier.Delete().Where(notifier.UserID(userID), notifier.ID(notifierID)).Exec(ctx) return err } diff --git a/backend/internal/data/repo/repo_users.go b/backend/internal/data/repo/repo_users.go index 68b1eb56..26c00e20 100644 --- a/backend/internal/data/repo/repo_users.go +++ b/backend/internal/data/repo/repo_users.go @@ -5,6 +5,7 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/data/ent" + "github.com/hay-kot/homebox/backend/internal/data/ent/actiontoken" "github.com/hay-kot/homebox/backend/internal/data/ent/user" ) @@ -60,9 +61,9 @@ func mapUserOut(user *ent.User) UserOut { } } -func (r *UserRepository) GetOneID(ctx context.Context, ID uuid.UUID) (UserOut, error) { +func (r *UserRepository) GetOneID(ctx context.Context, userID uuid.UUID) (UserOut, error) { return mapUserOutErr(r.db.User.Query(). - Where(user.ID(ID)). + Where(user.ID(userID)). WithGroup(). Only(ctx)) } @@ -101,9 +102,9 @@ func (r *UserRepository) Create(ctx context.Context, usr UserCreate) (UserOut, e return r.GetOneID(ctx, entUser.ID) } -func (r *UserRepository) Update(ctx context.Context, ID uuid.UUID, data UserUpdate) error { +func (r *UserRepository) Update(ctx context.Context, userID uuid.UUID, data UserUpdate) error { q := r.db.User.Update(). - Where(user.ID(ID)). + Where(user.ID(userID)). SetName(data.Name). SetEmail(data.Email) @@ -130,6 +131,28 @@ func (r *UserRepository) GetSuperusers(ctx context.Context) ([]*ent.User, error) return users, nil } -func (r *UserRepository) ChangePassword(ctx context.Context, UID uuid.UUID, pw string) error { - return r.db.User.UpdateOneID(UID).SetPassword(pw).Exec(ctx) +func (r *UserRepository) ChangePassword(ctx context.Context, userID uuid.UUID, pw string) error { + return r.db.User.UpdateOneID(userID).SetPassword(pw).Exec(ctx) +} + +func (r *UserRepository) PasswordResetCreate(ctx context.Context, userID uuid.UUID, token []byte) error { + return r.db.ActionToken.Create(). + SetUserID(userID). + SetToken(token). + SetAction(actiontoken.ActionResetPassword). + Exec(ctx) +} + +func (r *UserRepository) PasswordResetGet(ctx context.Context, token []byte) (*ent.ActionToken, error) { + return r.db.ActionToken.Query(). + Where(actiontoken.Token(token)). + WithUser(). + Only(ctx) +} + +func (r *UserRepository) PasswordResetDelete(ctx context.Context, token []byte) error { + _, err := r.db.ActionToken.Delete(). + Where(actiontoken.Token(token)). + Exec(ctx) + return err } diff --git a/backend/internal/sys/config/conf.go b/backend/internal/sys/config/conf.go index 8b7b23c3..98ea2629 100644 --- a/backend/internal/sys/config/conf.go +++ b/backend/internal/sys/config/conf.go @@ -18,13 +18,14 @@ const ( type Config struct { conf.Version - Mode string `yaml:"mode" conf:"default:development"` // development or production + Mode string `yaml:"mode" conf:"default:development"` // development or production Web WebConfig `yaml:"web"` Storage Storage `yaml:"storage"` Log LoggerConf `yaml:"logger"` Mailer MailerConf `yaml:"mailer"` Demo bool `yaml:"demo"` Debug DebugConf `yaml:"debug"` + BaseURL string `yaml:"base_url" conf:"default:http://localhost:3000"` Options Options `yaml:"options"` } diff --git a/backend/internal/sys/config/conf_mailer.go b/backend/internal/sys/config/conf_mailer.go index 1335a964..ef480edc 100644 --- a/backend/internal/sys/config/conf_mailer.go +++ b/backend/internal/sys/config/conf_mailer.go @@ -5,11 +5,11 @@ type MailerConf struct { Port int `conf:""` Username string `conf:""` Password string `conf:""` - From string `conf:""` + From string `conf:"info@example.com"` } // Ready is a simple check to ensure that the configuration is not empty. // or with it's default state. func (mc *MailerConf) Ready() bool { - return mc.Host != "" && mc.Port != 0 && mc.Username != "" && mc.Password != "" && mc.From != "" + return mc.Host != "" && mc.Port != 0 && mc.From != "" } diff --git a/backend/internal/sys/validate/errors.go b/backend/internal/sys/validate/errors.go index 09fdf2cc..c08a485d 100644 --- a/backend/internal/sys/validate/errors.go +++ b/backend/internal/sys/validate/errors.go @@ -5,8 +5,7 @@ import ( "errors" ) -type UnauthorizedError struct { -} +type UnauthorizedError struct{} func (err *UnauthorizedError) Error() string { return "unauthorized" diff --git a/backend/internal/sys/validate/validate.go b/backend/internal/sys/validate/validate.go index d9dbe24f..cf8a3df1 100644 --- a/backend/internal/sys/validate/validate.go +++ b/backend/internal/sys/validate/validate.go @@ -49,7 +49,6 @@ func init() { // nolint return false }) - if err != nil { panic(err) } diff --git a/backend/internal/web/adapters/adapters.go b/backend/internal/web/adapters/adapters.go index 8372a601..89176631 100644 --- a/backend/internal/web/adapters/adapters.go +++ b/backend/internal/web/adapters/adapters.go @@ -6,5 +6,7 @@ import ( "github.com/google/uuid" ) -type AdapterFunc[T any, Y any] func(*http.Request, T) (Y, error) -type IDFunc[T any, Y any] func(*http.Request, uuid.UUID, T) (Y, error) +type ( + AdapterFunc[T any, Y any] func(*http.Request, T) (Y, error) + IDFunc[T any, Y any] func(*http.Request, uuid.UUID, T) (Y, error) +) diff --git a/backend/internal/web/adapters/command.go b/backend/internal/web/adapters/command.go index d3d099bc..fa2942ff 100644 --- a/backend/internal/web/adapters/command.go +++ b/backend/internal/web/adapters/command.go @@ -8,8 +8,10 @@ import ( "github.com/hay-kot/httpkit/server" ) -type CommandFunc[T any] func(*http.Request) (T, error) -type CommandIDFunc[T any] func(*http.Request, uuid.UUID) (T, error) +type ( + CommandFunc[T any] func(*http.Request) (T, error) + CommandIDFunc[T any] func(*http.Request, uuid.UUID) (T, error) +) // Command is an HandlerAdapter that returns a errchain.HandlerFunc that // The command adapters are used to handle commands that do not accept a body diff --git a/backend/pkgs/mailer/mailer.go b/backend/pkgs/mailer/mailer.go index 9b593bc7..de8f26c8 100644 --- a/backend/pkgs/mailer/mailer.go +++ b/backend/pkgs/mailer/mailer.go @@ -1,4 +1,4 @@ -// Package mailer provides a simple mailer for sending emails. +// Package mailer provides a simple interface to send emails using SMTP. package mailer import ( @@ -25,16 +25,16 @@ func (m *Mailer) server() string { return m.Host + ":" + strconv.Itoa(m.Port) } -func (m *Mailer) Send(msg *Message) error { +func (m *Mailer) Send(msg Message) error { server := m.server() - header := make(map[string]string) - header["From"] = msg.From.String() - header["To"] = msg.To.String() - header["Subject"] = mime.QEncoding.Encode("UTF-8", msg.Subject) - header["MIME-Version"] = "1.0" - header["Content-Type"] = "text/html; charset=\"utf-8\"" - header["Content-Transfer-Encoding"] = "base64" + header := map[string]string{ + "From": m.From, + "Subject": mime.QEncoding.Encode("UTF-8", msg.Subject), + "MIME-Version": "1.0", + "Content-Type": "text/html; charset=\"utf-8\"", + "Content-Transfer-Encoding": "base64", + } message := "" for k, v := range header { @@ -46,7 +46,7 @@ func (m *Mailer) Send(msg *Message) error { server, smtp.PlainAuth("", m.Username, m.Password, m.Host), m.From, - []string{msg.To.Address}, + msg.ToAddresses(), []byte(message), ) } diff --git a/backend/pkgs/mailer/mailer_test.go b/backend/pkgs/mailer/mailer_test.go index 89e55cab..9a2db372 100644 --- a/backend/pkgs/mailer/mailer_test.go +++ b/backend/pkgs/mailer/mailer_test.go @@ -24,7 +24,6 @@ func GetTestMailer() (*Mailer, error) { // Unmarshal JSON err = json.Unmarshal(bytes, mailer) - if err != nil { return nil, err } diff --git a/backend/pkgs/mailer/message.go b/backend/pkgs/mailer/message.go index e0552b33..6a1a3e07 100644 --- a/backend/pkgs/mailer/message.go +++ b/backend/pkgs/mailer/message.go @@ -9,6 +9,10 @@ type Message struct { Body string } +func (m Message) ToAddresses() []string { + return []string{m.To.Address} +} + type MessageBuilder struct { subject string to mail.Address @@ -20,8 +24,8 @@ func NewMessageBuilder() *MessageBuilder { return &MessageBuilder{} } -func (mb *MessageBuilder) Build() *Message { - return &Message{ +func (mb *MessageBuilder) Build() Message { + return Message{ Subject: mb.subject, To: mb.to, From: mb.from, diff --git a/backend/pkgs/mailer/templates.go b/backend/pkgs/mailer/templates.go index cc5049f7..a892f2bf 100644 --- a/backend/pkgs/mailer/templates.go +++ b/backend/pkgs/mailer/templates.go @@ -48,7 +48,6 @@ func render(tpl string, data TemplateProps) (string, error) { var tplBuffer bytes.Buffer err = tmpl.Execute(&tplBuffer, data) - if err != nil { return "", err } diff --git a/docs/docs/api/openapi-2.0.json b/docs/docs/api/openapi-2.0.json index b10c93ad..e87d3b36 100644 --- a/docs/docs/api/openapi-2.0.json +++ b/docs/docs/api/openapi-2.0.json @@ -1785,6 +1785,33 @@ } } }, + "/v1/users/request-password-reset": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "User" + ], + "summary": "Request Password Reset", + "parameters": [ + { + "description": "User Data", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/services.PasswordResetRequest" + } + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, "/v1/users/self": { "get": { "security": [ @@ -2818,6 +2845,14 @@ } } }, + "services.PasswordResetRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + } + } + }, "services.UserRegistration": { "type": "object", "properties": { diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index c567952c..e6d55b78 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -1,4 +1,5 @@ module.exports = { + ignorePatterns: ["nuxt.proxyoverride.ts"], env: { browser: true, es2021: true, diff --git a/frontend/composables/use-notifier.ts b/frontend/composables/use-notifier.ts index 37243dac..6e9e0ed5 100644 --- a/frontend/composables/use-notifier.ts +++ b/frontend/composables/use-notifier.ts @@ -1,5 +1,3 @@ -import { useId } from "./use-ids"; - interface Notification { id: string; message: string; diff --git a/frontend/layouts/center-card.vue b/frontend/layouts/center-card.vue new file mode 100644 index 00000000..36d79ade --- /dev/null +++ b/frontend/layouts/center-card.vue @@ -0,0 +1,66 @@ + + diff --git a/frontend/layouts/empty.vue b/frontend/layouts/empty.vue index a263ec66..535e9a60 100644 --- a/frontend/layouts/empty.vue +++ b/frontend/layouts/empty.vue @@ -1,4 +1,3 @@ -