From 439cf333a1f1ff449cd1dfe6dfce7fa2eeaa2760 Mon Sep 17 00:00:00 2001 From: Bailin He <15058035+bailinhe@users.noreply.github.com> Date: Wed, 9 Aug 2023 10:39:29 -0400 Subject: [PATCH] DCS-52: user notification preferences (#15) * remove outdated entry * add notification tables * add notification types * add notification targets * add notification preferences * add notification preferences table * add create and update * change package name for oss * add default preferences handling * add audit log handling * add notification-preferences routes * add authenticated user notifications routes * add notification types client * fix error notification types or targets empty * fix linter errors * add notification type client * add notification targets client * rebase main * add notification preferences client * add tests * refactor per suggestions * use bool pointer to ensure value is provided * Update pkg/api/v1alpha1/authenticated_user.go Co-authored-by: E Camden Fisher * validate notification preference len before update * update per review * simplify indices * update per review --------- Co-authored-by: E Camden Fisher --- .gitignore | 2 + Makefile | 4 +- db/migrations/00012_orgs_deleted_at.sql | 2 +- .../00031_notification_preferences.sql | 76 + go.mod | 54 +- go.sum | 154 +- internal/dbtools/hooks.go | 152 ++ internal/dbtools/notification_preferences.go | 308 ++++ .../dbtools/notification_preferences_test.go | 400 +++++ internal/models/boil_table_names.go | 6 + internal/models/notification_preferences.go | 1528 +++++++++++++++++ internal/models/notification_targets.go | 1268 ++++++++++++++ internal/models/notification_types.go | 1194 +++++++++++++ internal/models/users.go | 191 +++ pkg/api/v1alpha1/authenticated_user.go | 45 +- pkg/api/v1alpha1/notification_preferences.go | 117 ++ pkg/api/v1alpha1/notifications_target.go | 472 +++++ pkg/api/v1alpha1/notifications_types.go | 472 +++++ pkg/api/v1alpha1/router.go | 90 + pkg/api/v1alpha1/users.go | 22 +- pkg/client/errors.go | 12 + pkg/client/notification_preferences.go | 51 + pkg/client/notification_preferences_test.go | 149 ++ pkg/client/notification_targets.go | 239 +++ pkg/client/notification_targets_test.go | 544 ++++++ pkg/client/notification_types.go | 239 +++ pkg/client/notification_types_test.go | 538 ++++++ pkg/events/v1alpha1/events.go | 22 +- sqlboiler.toml | 2 +- 29 files changed, 8219 insertions(+), 134 deletions(-) create mode 100644 db/migrations/00031_notification_preferences.sql create mode 100644 internal/dbtools/notification_preferences.go create mode 100644 internal/dbtools/notification_preferences_test.go create mode 100644 internal/models/notification_preferences.go create mode 100644 internal/models/notification_targets.go create mode 100644 internal/models/notification_types.go create mode 100644 pkg/api/v1alpha1/notification_preferences.go create mode 100644 pkg/api/v1alpha1/notifications_target.go create mode 100644 pkg/api/v1alpha1/notifications_types.go create mode 100644 pkg/client/notification_preferences.go create mode 100644 pkg/client/notification_preferences_test.go create mode 100644 pkg/client/notification_targets.go create mode 100644 pkg/client/notification_targets_test.go create mode 100644 pkg/client/notification_types.go create mode 100644 pkg/client/notification_types_test.go diff --git a/.gitignore b/.gitignore index 6604105..8290aff 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ coverage.txt audit.log .devcontainer/nats/resolver.conf .devcontainer/nsc + +tmp/** diff --git a/Makefile b/Makefile index be4fc89..479e985 100644 --- a/Makefile +++ b/Makefile @@ -85,7 +85,7 @@ dev-hydra: | echo "strategies:\n access_token: jwt\n" >> ./hydra/hydra.yml; \ echo "oidc:\n subject_identifiers:\n supported_types:\n - public\n" >> ./hydra/hydra.yml; \ fi; - @docker-compose up -d hydra-migrate hydra consent + @docker-compose up -d hydra-migrate hydra @sleep 10 @echo creating hydra client-id governor and client-secret ${SECRET} @docker-compose exec hydra hydra clients create \ @@ -145,4 +145,4 @@ test-local-init: @cockroach sql --database defaultdb --insecure -f testing/local_init.sql dev-serve: - go run . serve --config .governor-dev.yaml --audit-log-path audit.log \ No newline at end of file + go run . serve --config .governor-dev.yaml --audit-log-path audit.log diff --git a/db/migrations/00012_orgs_deleted_at.sql b/db/migrations/00012_orgs_deleted_at.sql index 00aae29..d64965a 100644 --- a/db/migrations/00012_orgs_deleted_at.sql +++ b/db/migrations/00012_orgs_deleted_at.sql @@ -3,7 +3,7 @@ ALTER TABLE organizations ADD COLUMN deleted_at TIMESTAMPTZ NULL; ALTER TABLE organizations ALTER COLUMN slug SET NOT NULL; -- +goose StatementEnd -​ + -- +goose Down -- +goose StatementBegin ALTER TABLE organizations DROP COLUMN deleted_at TIMESTAMPTZ NULL; diff --git a/db/migrations/00031_notification_preferences.sql b/db/migrations/00031_notification_preferences.sql new file mode 100644 index 0000000..d7d309c --- /dev/null +++ b/db/migrations/00031_notification_preferences.sql @@ -0,0 +1,76 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE notification_types ( + id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(), + name STRING NOT NULL, + slug STRING NOT NULL, + description STRING NOT NULL, + default_enabled BOOL NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + deleted_at TIMESTAMPTZ NULL, + + CONSTRAINT notification_types_slug_key UNIQUE (slug) WHERE deleted_at IS NULL, + INDEX (deleted_at) STORING (slug, default_enabled) +); + +CREATE TABLE notification_targets ( + id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(), + name STRING NOT NULL, + slug STRING NOT NULL, + description STRING NOT NULL, + default_enabled BOOL NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + deleted_at TIMESTAMPTZ NULL, + + CONSTRAINT notification_targets_slug_key UNIQUE (slug) WHERE deleted_at IS NULL, + INDEX (deleted_at) STORING (slug, default_enabled) +); + +CREATE TABLE notification_preferences ( + id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE, + notification_type_id UUID NOT NULL REFERENCES notification_types(id) ON DELETE CASCADE ON UPDATE CASCADE, + notification_target_id UUID NULL REFERENCES notification_targets(id) ON DELETE CASCADE ON UPDATE CASCADE, + notification_target_id_null_string UUID AS (IFNULL(notification_target_id, '00000000-0000-0000-0000-000000000000')) STORED, + enabled BOOL NOT NULL DEFAULT false, + + CONSTRAINT unique_user_type_target UNIQUE (user_id, notification_type_id, notification_target_id_null_string), + INDEX (user_id, notification_target_id, notification_type_id) STORING (enabled, notification_target_id_null_string) +); + +CREATE MATERIALIZED VIEW notification_defaults AS +SELECT + targets.id AS target_id, + targets.slug AS target_slug, + types.id AS type_id, + types.slug AS type_slug, + targets.default_enabled AS default_enabled +FROM + notification_types AS types +CROSS JOIN notification_targets AS targets +WHERE types.deleted_at IS NULL AND targets.deleted_at IS NULL +UNION ( + SELECT + '00000000-0000-0000-0000-000000000000' AS target_id, + '' AS target_slug, + types.id AS type_id, + types.slug AS type_slug, + types.default_enabled AS default_enabled + FROM + notification_types AS types + WHERE types.deleted_at IS NULL +); + +CREATE INDEX ON notification_defaults (target_id, type_id) STORING (target_slug, type_slug, default_enabled); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP MATERIALIZED VIEW notification_defaults; +DROP TABLE notification_preferences; +DROP TABLE notification_targets; +DROP TABLE notification_types; +-- +goose StatementEnd diff --git a/go.mod b/go.mod index e0a4d27..ef41c1f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/XSAM/otelsql v0.23.0 github.com/cockroachdb/cockroach-go/v2 v2.3.5 - github.com/coreos/go-oidc/v3 v3.5.0 + github.com/coreos/go-oidc/v3 v3.6.0 github.com/friendsofgo/errors v0.9.2 github.com/gin-contrib/cors v1.4.0 github.com/gin-contrib/zap v0.1.0 @@ -15,20 +15,20 @@ require ( github.com/gosimple/slug v1.13.1 github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 - github.com/metal-toolbox/auditevent v0.7.0 + github.com/metal-toolbox/auditevent v0.8.0 github.com/mitchellh/go-homedir v1.1.0 - github.com/nats-io/nats.go v1.25.0 - github.com/pressly/goose/v3 v3.10.0 + github.com/nats-io/nats.go v1.28.0 + github.com/pressly/goose/v3 v3.14.0 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.15.0 + github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 github.com/volatiletech/null/v8 v8.1.2 github.com/volatiletech/sqlboiler/v4 v4.14.2 github.com/volatiletech/strmangle v0.0.5 github.com/zsais/go-gin-prometheus v0.1.0 - go.hollow.sh/toolbox v0.5.1 - go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.40.0 + go.hollow.sh/toolbox v0.6.1 + go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0 go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 go.opentelemetry.io/otel/sdk v1.16.0 @@ -39,19 +39,22 @@ require ( require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/gofrs/flock v0.8.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 // indirect + github.com/klauspost/compress v1.16.7 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect - go.opentelemetry.io/proto/otlp v0.19.0 // indirect - google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect - google.golang.org/grpc v1.55.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf // indirect + google.golang.org/grpc v1.57.0 // indirect ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bytedance/sonic v1.9.1 // indirect + github.com/bytedance/sonic v1.10.0-rc3 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect @@ -60,17 +63,17 @@ require ( github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-logr/zapr v1.2.3 // indirect + github.com/go-logr/zapr v1.2.4 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/go-playground/validator/v10 v10.14.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/gosimple/unidecode v1.0.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.0 // indirect + github.com/jackc/pgconn v1.14.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.2 // indirect @@ -78,7 +81,7 @@ require ( github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/pgx/v4 v4.18.1 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.19 // indirect @@ -87,16 +90,15 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/nats-io/nats-server/v2 v2.8.4 // indirect github.com/nats-io/nkeys v0.4.4 // indirect github.com/nats-io/nuid v1.0.1 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.14.0 - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect + github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -108,9 +110,9 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect - go.uber.org/atomic v1.10.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/arch v0.3.0 // indirect + golang.org/x/arch v0.4.0 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/net v0.12.0 // indirect golang.org/x/sys v0.10.0 // indirect diff --git a/go.sum b/go.sum index fa2c257..4f11458 100644 --- a/go.sum +++ b/go.sum @@ -40,7 +40,6 @@ cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJW cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= @@ -90,8 +89,9 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0= +github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -100,8 +100,11 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -121,8 +124,8 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cockroach-go/v2 v2.3.5 h1:Khtm8K6fTTz/ZCWPzU9Ne3aOW9VyAnj4qIPCJgKtwK0= github.com/cockroachdb/cockroach-go/v2 v2.3.5/go.mod h1:1wNJ45eSXW9AnOc3skntW9ZUZz6gxrQK3cOj3rK+BC8= -github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw= -github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM= +github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o= +github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -189,8 +192,8 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= +github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= +github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= @@ -200,10 +203,10 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= +github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -225,7 +228,6 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2V github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -316,8 +318,8 @@ github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6 github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -367,8 +369,9 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= +github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -423,10 +426,12 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -480,8 +485,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/metal-toolbox/auditevent v0.7.0 h1:jLLnM1uBgNmxGpe06yqTqeKfgEyXCMA7O2OFZ5vtYik= -github.com/metal-toolbox/auditevent v0.7.0/go.mod h1:JeaoaxONjY0amUWfTnv0Z9werBvlI9kWG5sIrJIuVKc= +github.com/metal-toolbox/auditevent v0.8.0 h1:uKEwwYnxAEgvFNpleZzIblLymCDK44WwpsKi36mznVQ= +github.com/metal-toolbox/auditevent v0.8.0/go.mod h1:yladFT4ayNx+4wrNczvUjYFsg7Qvq+G8YBIDvjeeiLw= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -510,11 +515,10 @@ github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3P github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a h1:lem6QCvxR0Y28gth9P+wV2K/zYUUAkJ+55U8cpS0p5I= -github.com/nats-io/nats-server/v2 v2.8.4 h1:0jQzze1T9mECg8YZEl8+WYUXb9JKluJfCBriPUtluB4= -github.com/nats-io/nats-server/v2 v2.8.4/go.mod h1:8zZa+Al3WsESfmgSs98Fi06dRWLH5Bnq90m5bKD/eT4= -github.com/nats-io/nats.go v1.25.0 h1:t5/wCPGciR7X3Mu8QOi4jiJaXaWM8qtkLu4lzGZvYHE= -github.com/nats-io/nats.go v1.25.0/go.mod h1:D2WALIhz7V8M0pH8Scx8JZXlg6Oqz5VG+nQkK8nJdvg= +github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI= +github.com/nats-io/nats-server/v2 v2.9.15 h1:MuwEJheIwpvFgqvbs20W8Ish2azcygjf4Z0liVu2I4c= +github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c= +github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= @@ -524,8 +528,8 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -538,41 +542,41 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/pressly/goose/v3 v3.10.0 h1:Gn5E9CkPqTtWvfaDVqtJqMjYtsrZ9K5mU/8wzTsvg04= -github.com/pressly/goose/v3 v3.10.0/go.mod h1:c5D3a7j66cT0fhRPj7KsXolfduVrhLlxKZjmCVSey5w= +github.com/pressly/goose/v3 v3.14.0 h1:gNrFLLDF+fujdq394rcdYK3WPxp3VKWifTajlZwInJM= +github.com/pressly/goose/v3 v3.14.0/go.mod h1:uwSpREK867PbIsdE9GS6pRk1LUPB7gwMkmvk9/hbIMA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -589,8 +593,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= @@ -608,8 +612,8 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= -github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= -github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -626,7 +630,6 @@ github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= @@ -665,8 +668,8 @@ go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dY go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.hollow.sh/toolbox v0.5.1 h1:x/tRdpgUFckT/pw6FySxUpSFxhw+Sc66zxbwlra+br4= -go.hollow.sh/toolbox v0.5.1/go.mod h1:nfUNHobFfItossU5I0m7h2dw+0nY7rQt3DSUm2wGI5Q= +go.hollow.sh/toolbox v0.6.1 h1:3E6JofImSCe63XayczbGfDxIXUjmBziMBBmbwook8WA= +go.hollow.sh/toolbox v0.6.1/go.mod h1:nl+5RDDyYY/+wukOUzHHX2mOyWKRjlTOXUcGxny+tns= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -674,9 +677,9 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.40.0 h1:E4MMXDxufRnIHXhoTNOlNsdkWpC5HdLhfj84WNRKPkc= -go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.40.0/go.mod h1:A8+gHkpqTfMKxdKWq1pp360nAs096K26CH5Sm2YHDdA= -go.opentelemetry.io/contrib/propagators/b3 v1.15.0 h1:bMaonPyFcAvZ4EVzkUNkfnUHP5Zi63CIDlA3dRsEg8Q= +go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0 h1:l7AmwSVqozWKKXeZHycpdmpycQECRpoGwJ1FW2sWfTo= +go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0/go.mod h1:Ep4uoO2ijR0f49Pr7jAqyTjSCyS1SRL18wwttKfwqXA= +go.opentelemetry.io/contrib/propagators/b3 v1.17.0 h1:ImOVvHnku8jijXqkwCSyYKRDt2YrnGXD4BbhcpfbfJo= go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= @@ -695,16 +698,15 @@ go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/A go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -718,13 +720,12 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= +golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -785,7 +786,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -836,8 +837,6 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= @@ -861,7 +860,6 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -959,13 +957,11 @@ golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= @@ -973,7 +969,6 @@ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -984,14 +979,13 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1012,7 +1006,6 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1055,7 +1048,7 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1192,8 +1185,11 @@ google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e h1:xIXmWJ303kJCuogpj0bHq+dcjcZHU+XFyc1I0Yl9cRg= +google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf h1:xkVZ5FdZJF4U82Q/JS+DcZA83s/GRVL+QrFMlexk9Yo= +google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf h1:guOdSPaeFgN+jEJwTo1dQ71hdBm+yKSCCKuTRkJzcVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1220,13 +1216,12 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1279,19 +1274,19 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.14 h1:af6KNtFgsVmnDYrWk3PQCS9XT6BXe7o3ZFJKkIKvXNQ= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= @@ -1301,20 +1296,20 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= +modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= +modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= @@ -1323,6 +1318,7 @@ modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/internal/dbtools/hooks.go b/internal/dbtools/hooks.go index f13c51d..60fe246 100644 --- a/internal/dbtools/hooks.go +++ b/internal/dbtools/hooks.go @@ -2,6 +2,7 @@ package dbtools import ( "context" + "encoding/json" "fmt" "reflect" "time" @@ -815,3 +816,154 @@ func AuditGroupApplicationRequestRevoked(ctx context.Context, exec boil.ContextE return &event, event.Insert(ctx, exec, boil.Infer()) } + +// AuditNotificationTypeCreated inserts an event representing a notification type being created +func AuditNotificationTypeCreated(ctx context.Context, exec boil.ContextExecutor, pID string, actor *models.User, a *models.NotificationType) (*models.AuditEvent, error) { + // TODO non-user API actors don't exist in the governor database, + // we need to figure out how to handle that relationship in the audit table + var actorID null.String + if actor != nil { + actorID = null.StringFrom(actor.ID) + } + + event := models.AuditEvent{ + ParentID: null.StringFrom(pID), + ActorID: actorID, + Action: "notification_type.created", + Changeset: calculateChangeset(&models.NotificationType{}, a), + } + + return &event, event.Insert(ctx, exec, boil.Infer()) +} + +// AuditNotificationTypeDeleted inserts an event representing an notification type being deleted +func AuditNotificationTypeDeleted(ctx context.Context, exec boil.ContextExecutor, pID string, actor *models.User, a *models.NotificationType) (*models.AuditEvent, error) { + // TODO non-user API actors don't exist in the governor database, + // we need to figure out how to handle that relationship in the audit table + var actorID null.String + if actor != nil { + actorID = null.StringFrom(actor.ID) + } + + event := models.AuditEvent{ + ParentID: null.StringFrom(pID), + ActorID: actorID, + Action: "notification_type.deleted", + Changeset: calculateChangeset(a, &models.NotificationType{}), + } + + return &event, event.Insert(ctx, exec, boil.Infer()) +} + +// AuditNotificationTypeUpdated inserts an event representing notification type update into the events table +func AuditNotificationTypeUpdated(ctx context.Context, exec boil.ContextExecutor, pID string, actor *models.User, o, a *models.NotificationType) (*models.AuditEvent, error) { + // TODO non-user API actors don't exist in the governor database, + // we need to figure out how to handle that relationship in the audit table + var actorID null.String + if actor != nil { + actorID = null.StringFrom(actor.ID) + } + + event := models.AuditEvent{ + ParentID: null.StringFrom(pID), + ActorID: actorID, + Action: "notification_type.updated", + Changeset: calculateChangeset(o, a), + } + + return &event, event.Insert(ctx, exec, boil.Infer()) +} + +// AuditNotificationTargetCreated inserts an event representing a notification target being created +func AuditNotificationTargetCreated(ctx context.Context, exec boil.ContextExecutor, pID string, actor *models.User, a *models.NotificationTarget) (*models.AuditEvent, error) { + // TODO non-user API actors don't exist in the governor database, + // we need to figure out how to handle that relationship in the audit table + var actorID null.String + if actor != nil { + actorID = null.StringFrom(actor.ID) + } + + event := models.AuditEvent{ + ParentID: null.StringFrom(pID), + ActorID: actorID, + Action: "notification_target.created", + Changeset: calculateChangeset(&models.NotificationTarget{}, a), + } + + return &event, event.Insert(ctx, exec, boil.Infer()) +} + +// AuditNotificationTargetDeleted inserts an event representing an notification target being deleted +func AuditNotificationTargetDeleted(ctx context.Context, exec boil.ContextExecutor, pID string, actor *models.User, a *models.NotificationTarget) (*models.AuditEvent, error) { + // TODO non-user API actors don't exist in the governor database, + // we need to figure out how to handle that relationship in the audit table + var actorID null.String + if actor != nil { + actorID = null.StringFrom(actor.ID) + } + + event := models.AuditEvent{ + ParentID: null.StringFrom(pID), + ActorID: actorID, + Action: "notification_target.deleted", + Changeset: calculateChangeset(a, &models.NotificationTarget{}), + } + + return &event, event.Insert(ctx, exec, boil.Infer()) +} + +// AuditNotificationTargetUpdated inserts an event representing notification target update into the events table +func AuditNotificationTargetUpdated(ctx context.Context, exec boil.ContextExecutor, pID string, actor *models.User, o, a *models.NotificationTarget) (*models.AuditEvent, error) { + // TODO non-user API actors don't exist in the governor database, + // we need to figure out how to handle that relationship in the audit table + var actorID null.String + if actor != nil { + actorID = null.StringFrom(actor.ID) + } + + event := models.AuditEvent{ + ParentID: null.StringFrom(pID), + ActorID: actorID, + Action: "notification_target.updated", + Changeset: calculateChangeset(o, a), + } + + return &event, event.Insert(ctx, exec, boil.Infer()) +} + +// AuditNotificationPreferencesUpdated inserts an event representing notification preferences update into the events table +func AuditNotificationPreferencesUpdated(ctx context.Context, exec boil.ContextExecutor, pID string, actor *models.User, userID string, o, a UserNotificationPreferences) (*models.AuditEvent, error) { + // TODO non-user API actors don't exist in the governor database, + // we need to figure out how to handle that relationship in the audit table + type userNotificationPreferencesAuditRecord struct { + Preferences string `json:"preferences"` + } + + beforeJSON, err := json.Marshal(o) + if err != nil { + return nil, err + } + + afterJSON, err := json.Marshal(a) + if err != nil { + return nil, err + } + + before := &userNotificationPreferencesAuditRecord{Preferences: string(beforeJSON)} + after := &userNotificationPreferencesAuditRecord{Preferences: string(afterJSON)} + + var actorID null.String + if actor != nil { + actorID = null.StringFrom(actor.ID) + } + + event := models.AuditEvent{ + ParentID: null.StringFrom(pID), + ActorID: actorID, + Action: "notification_preferences.updated", + SubjectUserID: null.NewString(userID, true), + Changeset: calculateChangeset(before, after), + } + + return &event, event.Insert(ctx, exec, boil.Infer()) +} diff --git a/internal/dbtools/notification_preferences.go b/internal/dbtools/notification_preferences.go new file mode 100644 index 0000000..2833a13 --- /dev/null +++ b/internal/dbtools/notification_preferences.go @@ -0,0 +1,308 @@ +package dbtools + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "strings" + "sync" + + "github.com/metal-toolbox/governor-api/internal/models" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" +) + +// ErrDBUpdateNotificationPreferences is returned when there's an error occurred +// while updating the user notification preferences on database +var ErrDBUpdateNotificationPreferences = errors.New("an error occurred while updating the user notification preferences") + +func newErrDBUpdateNotificationPreferences(msg string) error { + return fmt.Errorf("%w: %s", ErrDBUpdateNotificationPreferences, msg) +} + +// ErrDBGetNotificationPreferences is returned when there's an error occurred +// while fetching the user notification preferences on database +var ErrDBGetNotificationPreferences = errors.New("an error occurred while fetching the user notification preferences") + +func newErrDBGetNotificationPreferences(msg string) error { + return fmt.Errorf("%w: %s", ErrDBGetNotificationPreferences, msg) +} + +// UserNotificationPreferenceTarget is the user notification target response +type UserNotificationPreferenceTarget struct { + Target string `json:"target"` + Enabled *bool `json:"enabled"` +} + +// UserNotificationPreferenceTargets is an alias for user notification target +// slice +type UserNotificationPreferenceTargets []*UserNotificationPreferenceTarget + +// UserNotificationPreference is the user notification preference response +type UserNotificationPreference struct { + NotificationType string `json:"notification_type" boil:"notification_type"` + Enabled *bool `json:"enabled"` + + NotificationTargets UserNotificationPreferenceTargets `json:"notification_targets"` +} + +// UserNotificationPreferences is an alias for user notification +// preference slice +type UserNotificationPreferences []*UserNotificationPreference + +// RefreshNotificationDefaults refreshes the notification_defaults +// materialized view +func RefreshNotificationDefaults(ctx context.Context, ex boil.ContextExecutor) (err error) { + q := queries.Raw("REFRESH MATERIALIZED VIEW notification_defaults") + _, err = q.ExecContext(ctx, ex) + + return +} + +// GetNotificationPreferences fetch a user's notification preferences from +// the governor DB, this function only gets rules the user had modified if +// `withDefaults` is false +func GetNotificationPreferences(ctx context.Context, uid string, ex boil.ContextExecutor, withDefaults bool) (UserNotificationPreferences, error) { + type preferencesQueryRecord struct { + NotificationType string `boil:"notification_type"` + NotificationTargetsJSON json.RawMessage `boil:"notification_targets"` + } + + type preferencesQueryRecordNotificationTarget struct { + Target string `json:"f1"` + Enabled *bool `json:"f2"` + } + + np := ` + WITH np as ( + SELECT + user_id, + notification_types.id as type_id, + notification_types.slug as type_slug, + notification_preferences.notification_target_id_null_string as target_id, + notification_targets.slug as target_slug, + enabled + FROM notification_preferences + LEFT JOIN notification_targets ON notification_target_id = notification_targets.id + LEFT JOIN notification_types ON notification_type_id = notification_types.id + WHERE notification_preferences.user_id = $1 + ) + ` + + qWithDefaults := fmt.Sprintf( + "%s\n%s", np, + ` + SELECT + nd.type_slug AS notification_type, + jsonb_agg((nd.target_slug, IFNULL(np.enabled, nd.default_enabled))) AS notification_targets + FROM notification_defaults as nd + FULL OUTER JOIN np on (np.target_id = nd.target_id AND np.type_id = nd.type_id) + GROUP BY nd.type_slug + `, + ) + + qWithoutDefaults := fmt.Sprintf( + "%s\n%s", np, + ` + SELECT + np.type_slug AS notification_type, + jsonb_agg((np.target_slug, np.enabled)) AS notification_targets + FROM np + GROUP BY np.type_slug + `, + ) + + var q *queries.Query + if withDefaults { + q = queries.Raw(qWithDefaults, uid) + } else { + q = queries.Raw(qWithoutDefaults, uid) + } + + records := []*preferencesQueryRecord{} + + if err := q.Bind(ctx, ex, &records); err != nil { + return nil, err + } + + wg := &sync.WaitGroup{} + preferences := make(UserNotificationPreferences, len(records)) + errs := make([]string, len(records)) + buildNotificationPreferenceRecord := func(i int, r *preferencesQueryRecord) { + defer wg.Done() + + targets := []*preferencesQueryRecordNotificationTarget{} + if err := json.Unmarshal(r.NotificationTargetsJSON, &targets); err != nil { + errs[i] = fmt.Sprintf("%s\n", err.Error()) + } + + p := new(UserNotificationPreference) + p.NotificationType = r.NotificationType + p.NotificationTargets = UserNotificationPreferenceTargets{} + + for _, t := range targets { + // for rows with NULL target indicates configs are for the parent notification type + if t.Target == "" { + p.Enabled = t.Enabled + continue + } + + target := new(UserNotificationPreferenceTarget) + *target = UserNotificationPreferenceTarget(*t) + p.NotificationTargets = append(p.NotificationTargets, target) + } + + preferences[i] = p + } + + for i, r := range records { + wg.Add(1) + + go buildNotificationPreferenceRecord(i, r) + } + + wg.Wait() + + errStr := strings.Join(errs, " ") + if strings.TrimSpace(errStr) != "" { + return nil, newErrDBGetNotificationPreferences(errStr) + } + + return preferences, nil +} + +func slugToIDMap(ctx context.Context, table string, db boil.ContextExecutor) (slugIDMap map[string]string, err error) { + rec := &struct { + Map json.RawMessage `boil:"map"` + }{} + + q := models.NewQuery( + qm.Select( + "jsonb_object_agg(slug, id) as map", + ), + qm.From(table), + qm.Where("deleted_at IS NULL"), + ) + + if err = q.Bind(ctx, db, rec); err != nil { + return + } + + err = json.Unmarshal(rec.Map, &slugIDMap) + + return +} + +// CreateOrUpdateNotificationPreferences updates a user's notification +// preferences, or creates the preferences if not exists +func CreateOrUpdateNotificationPreferences( + ctx context.Context, + user *models.User, + notificationPreferences UserNotificationPreferences, + ex boil.ContextExecutor, + auditID string, + actor *models.User, +) (*models.AuditEvent, error) { + typeSlugToID, err := slugToIDMap(ctx, models.TableNames.NotificationTypes, ex) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, err + } + + targetSlugToID, err := slugToIDMap(ctx, models.TableNames.NotificationTargets, ex) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, err + } + + curr, err := GetNotificationPreferences(ctx, user.ID, ex, false) + if err != nil { + return nil, err + } + + for _, p := range notificationPreferences { + notificationTypeID, ok := typeSlugToID[p.NotificationType] + if !ok { + return nil, + newErrDBUpdateNotificationPreferences(fmt.Sprintf("notificationType %s not found", p.NotificationType)) + } + + if p.Enabled == nil { + return nil, newErrDBUpdateNotificationPreferences(fmt.Sprintf( + "notification type %s enabled value cannot be empty", + p.NotificationType, + )) + } + + np := &models.NotificationPreference{ + UserID: user.ID, + NotificationTypeID: notificationTypeID, + NotificationTargetID: null.NewString("", false), + Enabled: *p.Enabled, + } + + if err := np.Upsert( + ctx, + ex, + true, + []string{ + models.NotificationPreferenceColumns.UserID, + models.NotificationPreferenceColumns.NotificationTypeID, + models.NotificationPreferenceColumns.NotificationTargetIDNullString, + }, + boil.Whitelist(models.NotificationPreferenceColumns.Enabled), + boil.Infer(), + ); err != nil { + return nil, err + } + + for _, t := range p.NotificationTargets { + notificationTargetID, ok := targetSlugToID[t.Target] + if !ok { + return nil, + newErrDBUpdateNotificationPreferences(fmt.Sprintf("notificationTarget %s not found", t.Target)) + } + + if t.Enabled == nil { + return nil, newErrDBUpdateNotificationPreferences(fmt.Sprintf( + "notification type [%s] target [%s] enabled value cannot be empty", + p.NotificationType, + t.Target, + )) + } + + np := &models.NotificationPreference{ + UserID: user.ID, + NotificationTypeID: notificationTypeID, + NotificationTargetID: null.NewString(notificationTargetID, true), + Enabled: *t.Enabled, + } + + if err := np.Upsert( + ctx, + ex, + true, + []string{ + models.NotificationPreferenceColumns.UserID, + models.NotificationPreferenceColumns.NotificationTypeID, + models.NotificationPreferenceColumns.NotificationTargetIDNullString, + }, + boil.Whitelist(models.NotificationPreferenceColumns.Enabled), + boil.Infer(), + ); err != nil { + return nil, err + } + } + } + + audit, err := AuditNotificationPreferencesUpdated( + ctx, ex, auditID, actor, user.ID, curr, notificationPreferences, + ) + if err != nil { + return nil, err + } + + return audit, nil +} diff --git a/internal/dbtools/notification_preferences_test.go b/internal/dbtools/notification_preferences_test.go new file mode 100644 index 0000000..7361dc1 --- /dev/null +++ b/internal/dbtools/notification_preferences_test.go @@ -0,0 +1,400 @@ +package dbtools + +import ( + "context" + "database/sql" + "testing" + + "github.com/cockroachdb/cockroach-go/v2/testserver" + "github.com/jmoiron/sqlx" + dbm "github.com/metal-toolbox/governor-api/db" + "github.com/metal-toolbox/governor-api/internal/models" + "github.com/pressly/goose/v3" + "github.com/stretchr/testify/suite" +) + +// NotificationPreferencesTestSuite is a test suite to run unit tests on +// all notification preferences db tests +type NotificationPreferencesTestSuite struct { + suite.Suite + + db *sql.DB + uid string + auditID string + + trueptr *bool + falseptr *bool +} + +func (s *NotificationPreferencesTestSuite) seedTestDB() error { + testData := []string{ + `INSERT INTO "users" ("id", "external_id", "name", "email", "login_count", "avatar_url", "last_login_at", "created_at", "updated_at", "github_id", "github_username", "deleted_at", "status") VALUES + ('00000001-0000-0000-0000-000000000001', NULL, 'User1', 'user1@email.com', 0, NULL, NULL, '2023-07-12 12:00:00.000000+00', '2023-07-12 12:00:00.000000+00', NULL, NULL, NULL, 'active');`, + `INSERT INTO "users" ("id", "external_id", "name", "email", "login_count", "avatar_url", "last_login_at", "created_at", "updated_at", "github_id", "github_username", "deleted_at", "status") VALUES + ('00000001-0000-0000-0000-000000000002', NULL, 'User2', 'user2@email.com', 0, NULL, NULL, '2023-07-12 12:00:00.000000+00', '2023-07-12 12:00:00.000000+00', NULL, NULL, NULL, 'active');`, + `INSERT INTO "users" ("id", "external_id", "name", "email", "login_count", "avatar_url", "last_login_at", "created_at", "updated_at", "github_id", "github_username", "deleted_at", "status") VALUES + ('00000001-0000-0000-0000-000000000003', NULL, 'User3', 'user3@email.com', 0, NULL, NULL, '2023-07-12 12:00:00.000000+00', '2023-07-12 12:00:00.000000+00', NULL, NULL, NULL, 'active');`, + `INSERT INTO "users" ("id", "external_id", "name", "email", "login_count", "avatar_url", "last_login_at", "created_at", "updated_at", "github_id", "github_username", "deleted_at", "status") VALUES + ('00000001-0000-0000-0000-000000000004', NULL, 'User4', 'user4@email.com', 0, NULL, NULL, '2023-07-12 12:00:00.000000+00', '2023-07-12 12:00:00.000000+00', NULL, NULL, NULL, 'active');`, + } + for _, q := range testData { + _, err := s.db.Query(q) + if err != nil { + return err + } + } + + return nil +} + +func (s *NotificationPreferencesTestSuite) SetupSuite() { + ts, err := testserver.NewTestServer() + if err != nil { + panic(err) + } + + s.db, err = sql.Open("postgres", ts.PGURL().String()) + if err != nil { + panic(err) + } + + goose.SetBaseFS(dbm.Migrations) + + if err := goose.Up(s.db, "migrations"); err != nil { + panic("migration failed - could not set up test db") + } + + if err := s.seedTestDB(); err != nil { + panic("db setup failed - could not seed test db") + } + + s.uid = "00000001-0000-0000-0000-000000000001" + s.auditID = "10000001-0000-0000-0000-000000000001" + + s.trueptr = new(bool) + s.falseptr = new(bool) + *s.trueptr = true + *s.falseptr = false +} + +func (s *NotificationPreferencesTestSuite) TestNotificationDefaults() { + tests := []struct { + name string + q string + want UserNotificationPreferences + wantQueryErr bool + wantFnErr bool + }{ + { + name: "insert notification type", + q: `INSERT INTO notification_types (id, name, slug, description, default_enabled) VALUES ('00000000-0000-0000-0000-000000000001', 'Alert', 'alert', 'aleeeeeeeert', 'false')`, + want: UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.falseptr, + NotificationTargets: UserNotificationPreferenceTargets{}, + }}, + wantQueryErr: false, + wantFnErr: false, + }, + { + name: "insert notification type duplication", + q: `INSERT INTO notification_types (id, name, slug, description, default_enabled) VALUES ('00000000-0000-0000-0000-000000000001', 'Alert', 'alert', 'aleeeeeeeert', 'false')`, + want: UserNotificationPreferences{nil}, + wantQueryErr: true, + wantFnErr: false, + }, + { + name: "update notification type defaults", + q: `UPDATE notification_types SET default_enabled = 'true' WHERE id = '00000000-0000-0000-0000-000000000001'`, + want: UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{}, + }}, + wantQueryErr: false, + wantFnErr: false, + }, + { + name: "insert notification target", + q: `INSERT INTO notification_targets (id, name, slug, description, default_enabled) VALUES ('00000000-0000-0000-0000-000000000001', 'Slack', 'slack', 'nice', 'false')`, + want: UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{{ + Target: "slack", + Enabled: s.falseptr, + }}, + }}, + wantQueryErr: false, + wantFnErr: false, + }, + { + name: "insert notification target duplication", + q: `INSERT INTO notification_targets (id, name, slug, description, default_enabled) VALUES ('00000000-0000-0000-0000-000000000001', 'Slack', 'slack', 'nice', 'true')`, + want: UserNotificationPreferences{nil}, + wantQueryErr: true, + wantFnErr: false, + }, + { + name: "update notification target defaults", + q: `UPDATE notification_targets SET default_enabled = 'true' WHERE id = '00000000-0000-0000-0000-000000000001'`, + want: UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{{ + Target: "slack", + Enabled: s.trueptr, + }}, + }}, + wantQueryErr: false, + wantFnErr: false, + }, + } + + for _, tc := range tests { + s.T().Run(tc.name, func(t *testing.T) { + _, err := s.db.Query(tc.q) + + if tc.wantQueryErr { + s.Assert().Error(err) + return + } + + ctx := context.TODO() + + if err := RefreshNotificationDefaults(ctx, s.db); err != nil { + panic(err) + } + + actual, err := GetNotificationPreferences(ctx, s.uid, s.db, true) + + if tc.wantFnErr { + s.Assert().Error(err) + return + } + + s.Assert().NoError(err) + s.Assert().Equal(len(tc.want), len(actual)) + + for i, p := range actual { + s.Assert().Equal(*tc.want[i], *p) + } + }) + } +} + +func (s *NotificationPreferencesTestSuite) TestNotificationPreferences() { + u := &models.User{ + ID: s.uid, + } + + sqlxdb := sqlx.NewDb(s.db, "postgres") + + tests := []struct { + name string + action func() error + wantWithDefaults UserNotificationPreferences + wantWithoutDefaults UserNotificationPreferences + wantErr bool + }{ + { + name: "get notification preferences", + action: nil, + wantWithDefaults: UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{{ + Target: "slack", + Enabled: s.trueptr, + }}, + }}, + wantWithoutDefaults: UserNotificationPreferences{}, + wantErr: false, + }, + { + name: "user updates notification type preferences", + action: func() error { + _, e := CreateOrUpdateNotificationPreferences( + context.TODO(), u, + UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + }}, + sqlxdb, s.auditID, u, + ) + + return e + }, + wantWithDefaults: UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{{ + Target: "slack", + Enabled: s.trueptr, + }}, + }}, + wantWithoutDefaults: UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{}, + }}, + wantErr: false, + }, + { + name: "user updates notification target preferences", + action: func() error { + _, e := CreateOrUpdateNotificationPreferences( + context.TODO(), u, + UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{{ + Target: "slack", + Enabled: s.falseptr, + }}, + }}, + sqlxdb, s.auditID, u, + ) + + return e + }, + wantWithDefaults: UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{{ + Target: "slack", + Enabled: s.falseptr, + }}, + }}, + wantWithoutDefaults: UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{{ + Target: "slack", + Enabled: s.falseptr, + }}, + }}, + wantErr: false, + }, + { + name: "user updates notification preferences with invalid type", + action: func() error { + _, e := CreateOrUpdateNotificationPreferences( + context.TODO(), u, + UserNotificationPreferences{{ + NotificationType: "invalid-type", + Enabled: s.trueptr, + }}, + sqlxdb, s.auditID, u, + ) + + return e + }, + wantWithDefaults: UserNotificationPreferences{nil}, + wantWithoutDefaults: UserNotificationPreferences{nil}, + wantErr: true, + }, + { + name: "user updates notification preferences with invalid target", + action: func() error { + _, e := CreateOrUpdateNotificationPreferences( + context.TODO(), u, + UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{{ + Target: "invalid-target", + Enabled: s.falseptr, + }}, + }}, + sqlxdb, s.auditID, u, + ) + + return e + }, + wantWithDefaults: UserNotificationPreferences{nil}, + wantWithoutDefaults: UserNotificationPreferences{nil}, + wantErr: true, + }, + { + name: "user updates notification preferences with empty target enabled value", + action: func() error { + _, e := CreateOrUpdateNotificationPreferences( + context.TODO(), u, + UserNotificationPreferences{{ + NotificationType: "alert", + Enabled: s.trueptr, + NotificationTargets: UserNotificationPreferenceTargets{{ + Target: "slack", + }}, + }}, + sqlxdb, s.auditID, u, + ) + + return e + }, + wantWithDefaults: UserNotificationPreferences{nil}, + wantWithoutDefaults: UserNotificationPreferences{nil}, + wantErr: true, + }, + { + name: "user updates notification preferences with empty type enabled value", + action: func() error { + _, e := CreateOrUpdateNotificationPreferences( + context.TODO(), u, + UserNotificationPreferences{{ + NotificationType: "alert", + NotificationTargets: UserNotificationPreferenceTargets{{ + Target: "slack", + Enabled: s.trueptr, + }}, + }}, + sqlxdb, s.auditID, u, + ) + + return e + }, + wantWithDefaults: UserNotificationPreferences{nil}, + wantWithoutDefaults: UserNotificationPreferences{nil}, + wantErr: true, + }, + } + + for _, tc := range tests { + s.T().Run(tc.name, func(t *testing.T) { + if tc.action != nil { + err := tc.action() + + if tc.wantErr { + s.Assert().Error(err) + return + } + + s.Assert().NoError(err) + } + + ctx := context.TODO() + + actualWithDefaults, err := GetNotificationPreferences(ctx, s.uid, s.db, true) + s.Assert().NoError(err) + + actualWithoutDefaults, err := GetNotificationPreferences(ctx, s.uid, s.db, false) + s.Assert().NoError(err) + + s.Assert().Equal(len(tc.wantWithDefaults), len(actualWithDefaults)) + s.Assert().Equal(len(tc.wantWithoutDefaults), len(actualWithoutDefaults)) + + for i, p := range actualWithDefaults { + s.Assert().Equal(*tc.wantWithDefaults[i], *p) + } + + for i, p := range actualWithoutDefaults { + s.Assert().Equal(*tc.wantWithoutDefaults[i], *p) + } + }) + } +} + +func TestNotificationPreferencesTestSuite(t *testing.T) { + suite.Run(t, new(NotificationPreferencesTestSuite)) +} diff --git a/internal/models/boil_table_names.go b/internal/models/boil_table_names.go index 2143815..c36a4b3 100644 --- a/internal/models/boil_table_names.go +++ b/internal/models/boil_table_names.go @@ -14,6 +14,9 @@ var TableNames = struct { GroupMemberships string GroupOrganizations string Groups string + NotificationPreferences string + NotificationTargets string + NotificationTypes string Organizations string Users string }{ @@ -27,6 +30,9 @@ var TableNames = struct { GroupMemberships: "group_memberships", GroupOrganizations: "group_organizations", Groups: "groups", + NotificationPreferences: "notification_preferences", + NotificationTargets: "notification_targets", + NotificationTypes: "notification_types", Organizations: "organizations", Users: "users", } diff --git a/internal/models/notification_preferences.go b/internal/models/notification_preferences.go new file mode 100644 index 0000000..db869f5 --- /dev/null +++ b/internal/models/notification_preferences.go @@ -0,0 +1,1528 @@ +// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// NotificationPreference is an object representing the database table. +type NotificationPreference struct { + ID string `boil:"id" json:"id" toml:"id" yaml:"id"` + UserID string `boil:"user_id" json:"user_id" toml:"user_id" yaml:"user_id"` + NotificationTypeID string `boil:"notification_type_id" json:"notification_type_id" toml:"notification_type_id" yaml:"notification_type_id"` + NotificationTargetID null.String `boil:"notification_target_id" json:"notification_target_id,omitempty" toml:"notification_target_id" yaml:"notification_target_id,omitempty"` + NotificationTargetIDNullString null.String `boil:"notification_target_id_null_string" json:"notification_target_id_null_string,omitempty" toml:"notification_target_id_null_string" yaml:"notification_target_id_null_string,omitempty"` + Enabled bool `boil:"enabled" json:"enabled" toml:"enabled" yaml:"enabled"` + + R *notificationPreferenceR `boil:"-" json:"-" toml:"-" yaml:"-"` + L notificationPreferenceL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var NotificationPreferenceColumns = struct { + ID string + UserID string + NotificationTypeID string + NotificationTargetID string + NotificationTargetIDNullString string + Enabled string +}{ + ID: "id", + UserID: "user_id", + NotificationTypeID: "notification_type_id", + NotificationTargetID: "notification_target_id", + NotificationTargetIDNullString: "notification_target_id_null_string", + Enabled: "enabled", +} + +var NotificationPreferenceTableColumns = struct { + ID string + UserID string + NotificationTypeID string + NotificationTargetID string + NotificationTargetIDNullString string + Enabled string +}{ + ID: "notification_preferences.id", + UserID: "notification_preferences.user_id", + NotificationTypeID: "notification_preferences.notification_type_id", + NotificationTargetID: "notification_preferences.notification_target_id", + NotificationTargetIDNullString: "notification_preferences.notification_target_id_null_string", + Enabled: "notification_preferences.enabled", +} + +// Generated where + +var NotificationPreferenceWhere = struct { + ID whereHelperstring + UserID whereHelperstring + NotificationTypeID whereHelperstring + NotificationTargetID whereHelpernull_String + NotificationTargetIDNullString whereHelpernull_String + Enabled whereHelperbool +}{ + ID: whereHelperstring{field: "\"notification_preferences\".\"id\""}, + UserID: whereHelperstring{field: "\"notification_preferences\".\"user_id\""}, + NotificationTypeID: whereHelperstring{field: "\"notification_preferences\".\"notification_type_id\""}, + NotificationTargetID: whereHelpernull_String{field: "\"notification_preferences\".\"notification_target_id\""}, + NotificationTargetIDNullString: whereHelpernull_String{field: "\"notification_preferences\".\"notification_target_id_null_string\""}, + Enabled: whereHelperbool{field: "\"notification_preferences\".\"enabled\""}, +} + +// NotificationPreferenceRels is where relationship names are stored. +var NotificationPreferenceRels = struct { + User string + NotificationType string + NotificationTarget string +}{ + User: "User", + NotificationType: "NotificationType", + NotificationTarget: "NotificationTarget", +} + +// notificationPreferenceR is where relationships are stored. +type notificationPreferenceR struct { + User *User `boil:"User" json:"User" toml:"User" yaml:"User"` + NotificationType *NotificationType `boil:"NotificationType" json:"NotificationType" toml:"NotificationType" yaml:"NotificationType"` + NotificationTarget *NotificationTarget `boil:"NotificationTarget" json:"NotificationTarget" toml:"NotificationTarget" yaml:"NotificationTarget"` +} + +// NewStruct creates a new relationship struct +func (*notificationPreferenceR) NewStruct() *notificationPreferenceR { + return ¬ificationPreferenceR{} +} + +func (r *notificationPreferenceR) GetUser() *User { + if r == nil { + return nil + } + return r.User +} + +func (r *notificationPreferenceR) GetNotificationType() *NotificationType { + if r == nil { + return nil + } + return r.NotificationType +} + +func (r *notificationPreferenceR) GetNotificationTarget() *NotificationTarget { + if r == nil { + return nil + } + return r.NotificationTarget +} + +// notificationPreferenceL is where Load methods for each relationship are stored. +type notificationPreferenceL struct{} + +var ( + notificationPreferenceAllColumns = []string{"id", "user_id", "notification_type_id", "notification_target_id", "notification_target_id_null_string", "enabled"} + notificationPreferenceColumnsWithoutDefault = []string{"user_id", "notification_type_id"} + notificationPreferenceColumnsWithDefault = []string{"id", "notification_target_id", "notification_target_id_null_string", "enabled"} + notificationPreferencePrimaryKeyColumns = []string{"id"} + notificationPreferenceGeneratedColumns = []string{} +) + +type ( + // NotificationPreferenceSlice is an alias for a slice of pointers to NotificationPreference. + // This should almost always be used instead of []NotificationPreference. + NotificationPreferenceSlice []*NotificationPreference + // NotificationPreferenceHook is the signature for custom NotificationPreference hook methods + NotificationPreferenceHook func(context.Context, boil.ContextExecutor, *NotificationPreference) error + + notificationPreferenceQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + notificationPreferenceType = reflect.TypeOf(&NotificationPreference{}) + notificationPreferenceMapping = queries.MakeStructMapping(notificationPreferenceType) + notificationPreferencePrimaryKeyMapping, _ = queries.BindMapping(notificationPreferenceType, notificationPreferenceMapping, notificationPreferencePrimaryKeyColumns) + notificationPreferenceInsertCacheMut sync.RWMutex + notificationPreferenceInsertCache = make(map[string]insertCache) + notificationPreferenceUpdateCacheMut sync.RWMutex + notificationPreferenceUpdateCache = make(map[string]updateCache) + notificationPreferenceUpsertCacheMut sync.RWMutex + notificationPreferenceUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +var notificationPreferenceAfterSelectHooks []NotificationPreferenceHook + +var notificationPreferenceBeforeInsertHooks []NotificationPreferenceHook +var notificationPreferenceAfterInsertHooks []NotificationPreferenceHook + +var notificationPreferenceBeforeUpdateHooks []NotificationPreferenceHook +var notificationPreferenceAfterUpdateHooks []NotificationPreferenceHook + +var notificationPreferenceBeforeDeleteHooks []NotificationPreferenceHook +var notificationPreferenceAfterDeleteHooks []NotificationPreferenceHook + +var notificationPreferenceBeforeUpsertHooks []NotificationPreferenceHook +var notificationPreferenceAfterUpsertHooks []NotificationPreferenceHook + +// doAfterSelectHooks executes all "after Select" hooks. +func (o *NotificationPreference) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationPreferenceAfterSelectHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeInsertHooks executes all "before insert" hooks. +func (o *NotificationPreference) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationPreferenceBeforeInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterInsertHooks executes all "after Insert" hooks. +func (o *NotificationPreference) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationPreferenceAfterInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpdateHooks executes all "before Update" hooks. +func (o *NotificationPreference) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationPreferenceBeforeUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpdateHooks executes all "after Update" hooks. +func (o *NotificationPreference) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationPreferenceAfterUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeDeleteHooks executes all "before Delete" hooks. +func (o *NotificationPreference) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationPreferenceBeforeDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterDeleteHooks executes all "after Delete" hooks. +func (o *NotificationPreference) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationPreferenceAfterDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpsertHooks executes all "before Upsert" hooks. +func (o *NotificationPreference) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationPreferenceBeforeUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpsertHooks executes all "after Upsert" hooks. +func (o *NotificationPreference) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationPreferenceAfterUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// AddNotificationPreferenceHook registers your hook function for all future operations. +func AddNotificationPreferenceHook(hookPoint boil.HookPoint, notificationPreferenceHook NotificationPreferenceHook) { + switch hookPoint { + case boil.AfterSelectHook: + notificationPreferenceAfterSelectHooks = append(notificationPreferenceAfterSelectHooks, notificationPreferenceHook) + case boil.BeforeInsertHook: + notificationPreferenceBeforeInsertHooks = append(notificationPreferenceBeforeInsertHooks, notificationPreferenceHook) + case boil.AfterInsertHook: + notificationPreferenceAfterInsertHooks = append(notificationPreferenceAfterInsertHooks, notificationPreferenceHook) + case boil.BeforeUpdateHook: + notificationPreferenceBeforeUpdateHooks = append(notificationPreferenceBeforeUpdateHooks, notificationPreferenceHook) + case boil.AfterUpdateHook: + notificationPreferenceAfterUpdateHooks = append(notificationPreferenceAfterUpdateHooks, notificationPreferenceHook) + case boil.BeforeDeleteHook: + notificationPreferenceBeforeDeleteHooks = append(notificationPreferenceBeforeDeleteHooks, notificationPreferenceHook) + case boil.AfterDeleteHook: + notificationPreferenceAfterDeleteHooks = append(notificationPreferenceAfterDeleteHooks, notificationPreferenceHook) + case boil.BeforeUpsertHook: + notificationPreferenceBeforeUpsertHooks = append(notificationPreferenceBeforeUpsertHooks, notificationPreferenceHook) + case boil.AfterUpsertHook: + notificationPreferenceAfterUpsertHooks = append(notificationPreferenceAfterUpsertHooks, notificationPreferenceHook) + } +} + +// One returns a single notificationPreference record from the query. +func (q notificationPreferenceQuery) One(ctx context.Context, exec boil.ContextExecutor) (*NotificationPreference, error) { + o := &NotificationPreference{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for notification_preferences") + } + + if err := o.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + + return o, nil +} + +// All returns all NotificationPreference records from the query. +func (q notificationPreferenceQuery) All(ctx context.Context, exec boil.ContextExecutor) (NotificationPreferenceSlice, error) { + var o []*NotificationPreference + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to NotificationPreference slice") + } + + if len(notificationPreferenceAfterSelectHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + } + } + + return o, nil +} + +// Count returns the count of all NotificationPreference records in the query. +func (q notificationPreferenceQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count notification_preferences rows") + } + + return count, nil +} + +// Exists checks if the row exists in the table. +func (q notificationPreferenceQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if notification_preferences exists") + } + + return count > 0, nil +} + +// User pointed to by the foreign key. +func (o *NotificationPreference) User(mods ...qm.QueryMod) userQuery { + queryMods := []qm.QueryMod{ + qm.Where("\"id\" = ?", o.UserID), + } + + queryMods = append(queryMods, mods...) + + return Users(queryMods...) +} + +// NotificationType pointed to by the foreign key. +func (o *NotificationPreference) NotificationType(mods ...qm.QueryMod) notificationTypeQuery { + queryMods := []qm.QueryMod{ + qm.Where("\"id\" = ?", o.NotificationTypeID), + } + + queryMods = append(queryMods, mods...) + + return NotificationTypes(queryMods...) +} + +// NotificationTarget pointed to by the foreign key. +func (o *NotificationPreference) NotificationTarget(mods ...qm.QueryMod) notificationTargetQuery { + queryMods := []qm.QueryMod{ + qm.Where("\"id\" = ?", o.NotificationTargetID), + } + + queryMods = append(queryMods, mods...) + + return NotificationTargets(queryMods...) +} + +// LoadUser allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for an N-1 relationship. +func (notificationPreferenceL) LoadUser(ctx context.Context, e boil.ContextExecutor, singular bool, maybeNotificationPreference interface{}, mods queries.Applicator) error { + var slice []*NotificationPreference + var object *NotificationPreference + + if singular { + var ok bool + object, ok = maybeNotificationPreference.(*NotificationPreference) + if !ok { + object = new(NotificationPreference) + ok = queries.SetFromEmbeddedStruct(&object, &maybeNotificationPreference) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeNotificationPreference)) + } + } + } else { + s, ok := maybeNotificationPreference.(*[]*NotificationPreference) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeNotificationPreference) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeNotificationPreference)) + } + } + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = ¬ificationPreferenceR{} + } + args = append(args, object.UserID) + + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = ¬ificationPreferenceR{} + } + + for _, a := range args { + if a == obj.UserID { + continue Outer + } + } + + args = append(args, obj.UserID) + + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery( + qm.From(`users`), + qm.WhereIn(`users.id in ?`, args...), + qmhelper.WhereIsNull(`users.deleted_at`), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load User") + } + + var resultSlice []*User + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice User") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results of eager load for users") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for users") + } + + if len(userAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + + if len(resultSlice) == 0 { + return nil + } + + if singular { + foreign := resultSlice[0] + object.R.User = foreign + if foreign.R == nil { + foreign.R = &userR{} + } + foreign.R.NotificationPreferences = append(foreign.R.NotificationPreferences, object) + return nil + } + + for _, local := range slice { + for _, foreign := range resultSlice { + if local.UserID == foreign.ID { + local.R.User = foreign + if foreign.R == nil { + foreign.R = &userR{} + } + foreign.R.NotificationPreferences = append(foreign.R.NotificationPreferences, local) + break + } + } + } + + return nil +} + +// LoadNotificationType allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for an N-1 relationship. +func (notificationPreferenceL) LoadNotificationType(ctx context.Context, e boil.ContextExecutor, singular bool, maybeNotificationPreference interface{}, mods queries.Applicator) error { + var slice []*NotificationPreference + var object *NotificationPreference + + if singular { + var ok bool + object, ok = maybeNotificationPreference.(*NotificationPreference) + if !ok { + object = new(NotificationPreference) + ok = queries.SetFromEmbeddedStruct(&object, &maybeNotificationPreference) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeNotificationPreference)) + } + } + } else { + s, ok := maybeNotificationPreference.(*[]*NotificationPreference) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeNotificationPreference) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeNotificationPreference)) + } + } + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = ¬ificationPreferenceR{} + } + args = append(args, object.NotificationTypeID) + + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = ¬ificationPreferenceR{} + } + + for _, a := range args { + if a == obj.NotificationTypeID { + continue Outer + } + } + + args = append(args, obj.NotificationTypeID) + + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery( + qm.From(`notification_types`), + qm.WhereIn(`notification_types.id in ?`, args...), + qmhelper.WhereIsNull(`notification_types.deleted_at`), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load NotificationType") + } + + var resultSlice []*NotificationType + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice NotificationType") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results of eager load for notification_types") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for notification_types") + } + + if len(notificationTypeAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + + if len(resultSlice) == 0 { + return nil + } + + if singular { + foreign := resultSlice[0] + object.R.NotificationType = foreign + if foreign.R == nil { + foreign.R = ¬ificationTypeR{} + } + foreign.R.NotificationPreferences = append(foreign.R.NotificationPreferences, object) + return nil + } + + for _, local := range slice { + for _, foreign := range resultSlice { + if local.NotificationTypeID == foreign.ID { + local.R.NotificationType = foreign + if foreign.R == nil { + foreign.R = ¬ificationTypeR{} + } + foreign.R.NotificationPreferences = append(foreign.R.NotificationPreferences, local) + break + } + } + } + + return nil +} + +// LoadNotificationTarget allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for an N-1 relationship. +func (notificationPreferenceL) LoadNotificationTarget(ctx context.Context, e boil.ContextExecutor, singular bool, maybeNotificationPreference interface{}, mods queries.Applicator) error { + var slice []*NotificationPreference + var object *NotificationPreference + + if singular { + var ok bool + object, ok = maybeNotificationPreference.(*NotificationPreference) + if !ok { + object = new(NotificationPreference) + ok = queries.SetFromEmbeddedStruct(&object, &maybeNotificationPreference) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeNotificationPreference)) + } + } + } else { + s, ok := maybeNotificationPreference.(*[]*NotificationPreference) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeNotificationPreference) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeNotificationPreference)) + } + } + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = ¬ificationPreferenceR{} + } + if !queries.IsNil(object.NotificationTargetID) { + args = append(args, object.NotificationTargetID) + } + + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = ¬ificationPreferenceR{} + } + + for _, a := range args { + if queries.Equal(a, obj.NotificationTargetID) { + continue Outer + } + } + + if !queries.IsNil(obj.NotificationTargetID) { + args = append(args, obj.NotificationTargetID) + } + + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery( + qm.From(`notification_targets`), + qm.WhereIn(`notification_targets.id in ?`, args...), + qmhelper.WhereIsNull(`notification_targets.deleted_at`), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load NotificationTarget") + } + + var resultSlice []*NotificationTarget + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice NotificationTarget") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results of eager load for notification_targets") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for notification_targets") + } + + if len(notificationTargetAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + + if len(resultSlice) == 0 { + return nil + } + + if singular { + foreign := resultSlice[0] + object.R.NotificationTarget = foreign + if foreign.R == nil { + foreign.R = ¬ificationTargetR{} + } + foreign.R.NotificationPreferences = append(foreign.R.NotificationPreferences, object) + return nil + } + + for _, local := range slice { + for _, foreign := range resultSlice { + if queries.Equal(local.NotificationTargetID, foreign.ID) { + local.R.NotificationTarget = foreign + if foreign.R == nil { + foreign.R = ¬ificationTargetR{} + } + foreign.R.NotificationPreferences = append(foreign.R.NotificationPreferences, local) + break + } + } + } + + return nil +} + +// SetUser of the notificationPreference to the related item. +// Sets o.R.User to related. +// Adds o to related.R.NotificationPreferences. +func (o *NotificationPreference) SetUser(ctx context.Context, exec boil.ContextExecutor, insert bool, related *User) error { + var err error + if insert { + if err = related.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } + + updateQuery := fmt.Sprintf( + "UPDATE \"notification_preferences\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"user_id"}), + strmangle.WhereClause("\"", "\"", 2, notificationPreferencePrimaryKeyColumns), + ) + values := []interface{}{related.ID, o.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update local table") + } + + o.UserID = related.ID + if o.R == nil { + o.R = ¬ificationPreferenceR{ + User: related, + } + } else { + o.R.User = related + } + + if related.R == nil { + related.R = &userR{ + NotificationPreferences: NotificationPreferenceSlice{o}, + } + } else { + related.R.NotificationPreferences = append(related.R.NotificationPreferences, o) + } + + return nil +} + +// SetNotificationType of the notificationPreference to the related item. +// Sets o.R.NotificationType to related. +// Adds o to related.R.NotificationPreferences. +func (o *NotificationPreference) SetNotificationType(ctx context.Context, exec boil.ContextExecutor, insert bool, related *NotificationType) error { + var err error + if insert { + if err = related.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } + + updateQuery := fmt.Sprintf( + "UPDATE \"notification_preferences\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"notification_type_id"}), + strmangle.WhereClause("\"", "\"", 2, notificationPreferencePrimaryKeyColumns), + ) + values := []interface{}{related.ID, o.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update local table") + } + + o.NotificationTypeID = related.ID + if o.R == nil { + o.R = ¬ificationPreferenceR{ + NotificationType: related, + } + } else { + o.R.NotificationType = related + } + + if related.R == nil { + related.R = ¬ificationTypeR{ + NotificationPreferences: NotificationPreferenceSlice{o}, + } + } else { + related.R.NotificationPreferences = append(related.R.NotificationPreferences, o) + } + + return nil +} + +// SetNotificationTarget of the notificationPreference to the related item. +// Sets o.R.NotificationTarget to related. +// Adds o to related.R.NotificationPreferences. +func (o *NotificationPreference) SetNotificationTarget(ctx context.Context, exec boil.ContextExecutor, insert bool, related *NotificationTarget) error { + var err error + if insert { + if err = related.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } + + updateQuery := fmt.Sprintf( + "UPDATE \"notification_preferences\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"notification_target_id"}), + strmangle.WhereClause("\"", "\"", 2, notificationPreferencePrimaryKeyColumns), + ) + values := []interface{}{related.ID, o.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update local table") + } + + queries.Assign(&o.NotificationTargetID, related.ID) + if o.R == nil { + o.R = ¬ificationPreferenceR{ + NotificationTarget: related, + } + } else { + o.R.NotificationTarget = related + } + + if related.R == nil { + related.R = ¬ificationTargetR{ + NotificationPreferences: NotificationPreferenceSlice{o}, + } + } else { + related.R.NotificationPreferences = append(related.R.NotificationPreferences, o) + } + + return nil +} + +// RemoveNotificationTarget relationship. +// Sets o.R.NotificationTarget to nil. +// Removes o from all passed in related items' relationships struct. +func (o *NotificationPreference) RemoveNotificationTarget(ctx context.Context, exec boil.ContextExecutor, related *NotificationTarget) error { + var err error + + queries.SetScanner(&o.NotificationTargetID, nil) + if _, err = o.Update(ctx, exec, boil.Whitelist("notification_target_id")); err != nil { + return errors.Wrap(err, "failed to update local table") + } + + if o.R != nil { + o.R.NotificationTarget = nil + } + if related == nil || related.R == nil { + return nil + } + + for i, ri := range related.R.NotificationPreferences { + if queries.Equal(o.NotificationTargetID, ri.NotificationTargetID) { + continue + } + + ln := len(related.R.NotificationPreferences) + if ln > 1 && i < ln-1 { + related.R.NotificationPreferences[i] = related.R.NotificationPreferences[ln-1] + } + related.R.NotificationPreferences = related.R.NotificationPreferences[:ln-1] + break + } + return nil +} + +// NotificationPreferences retrieves all the records using an executor. +func NotificationPreferences(mods ...qm.QueryMod) notificationPreferenceQuery { + mods = append(mods, qm.From("\"notification_preferences\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"notification_preferences\".*"}) + } + + return notificationPreferenceQuery{q} +} + +// FindNotificationPreference retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindNotificationPreference(ctx context.Context, exec boil.ContextExecutor, iD string, selectCols ...string) (*NotificationPreference, error) { + notificationPreferenceObj := &NotificationPreference{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"notification_preferences\" where \"id\"=$1", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, notificationPreferenceObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from notification_preferences") + } + + if err = notificationPreferenceObj.doAfterSelectHooks(ctx, exec); err != nil { + return notificationPreferenceObj, err + } + + return notificationPreferenceObj, nil +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *NotificationPreference) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no notification_preferences provided for insertion") + } + + var err error + + if err := o.doBeforeInsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(notificationPreferenceColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + notificationPreferenceInsertCacheMut.RLock() + cache, cached := notificationPreferenceInsertCache[key] + notificationPreferenceInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + notificationPreferenceAllColumns, + notificationPreferenceColumnsWithDefault, + notificationPreferenceColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(notificationPreferenceType, notificationPreferenceMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(notificationPreferenceType, notificationPreferenceMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"notification_preferences\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"notification_preferences\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into notification_preferences") + } + + if !cached { + notificationPreferenceInsertCacheMut.Lock() + notificationPreferenceInsertCache[key] = cache + notificationPreferenceInsertCacheMut.Unlock() + } + + return o.doAfterInsertHooks(ctx, exec) +} + +// Update uses an executor to update the NotificationPreference. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *NotificationPreference) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + var err error + if err = o.doBeforeUpdateHooks(ctx, exec); err != nil { + return 0, err + } + key := makeCacheKey(columns, nil) + notificationPreferenceUpdateCacheMut.RLock() + cache, cached := notificationPreferenceUpdateCache[key] + notificationPreferenceUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + notificationPreferenceAllColumns, + notificationPreferencePrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update notification_preferences, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"notification_preferences\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, notificationPreferencePrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(notificationPreferenceType, notificationPreferenceMapping, append(wl, notificationPreferencePrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update notification_preferences row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for notification_preferences") + } + + if !cached { + notificationPreferenceUpdateCacheMut.Lock() + notificationPreferenceUpdateCache[key] = cache + notificationPreferenceUpdateCacheMut.Unlock() + } + + return rowsAff, o.doAfterUpdateHooks(ctx, exec) +} + +// UpdateAll updates all rows with the specified column values. +func (q notificationPreferenceQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for notification_preferences") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for notification_preferences") + } + + return rowsAff, nil +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o NotificationPreferenceSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationPreferencePrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"notification_preferences\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, notificationPreferencePrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in notificationPreference slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all notificationPreference") + } + return rowsAff, nil +} + +// Delete deletes a single NotificationPreference record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *NotificationPreference) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no NotificationPreference provided for delete") + } + + if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), notificationPreferencePrimaryKeyMapping) + sql := "DELETE FROM \"notification_preferences\" WHERE \"id\"=$1" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from notification_preferences") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for notification_preferences") + } + + if err := o.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + return rowsAff, nil +} + +// DeleteAll deletes all matching rows. +func (q notificationPreferenceQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no notificationPreferenceQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from notification_preferences") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for notification_preferences") + } + + return rowsAff, nil +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o NotificationPreferenceSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + if len(notificationPreferenceBeforeDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationPreferencePrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"notification_preferences\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, notificationPreferencePrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from notificationPreference slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for notification_preferences") + } + + if len(notificationPreferenceAfterDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + return rowsAff, nil +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *NotificationPreference) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindNotificationPreference(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *NotificationPreferenceSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := NotificationPreferenceSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationPreferencePrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"notification_preferences\".* FROM \"notification_preferences\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, notificationPreferencePrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in NotificationPreferenceSlice") + } + + *o = slice + + return nil +} + +// NotificationPreferenceExists checks if the NotificationPreference row exists. +func NotificationPreferenceExists(ctx context.Context, exec boil.ContextExecutor, iD string) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"notification_preferences\" where \"id\"=$1 limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if notification_preferences exists") + } + + return exists, nil +} + +// Exists checks if the NotificationPreference row exists. +func (o *NotificationPreference) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return NotificationPreferenceExists(ctx, exec, o.ID) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *NotificationPreference) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns) error { + if o == nil { + return errors.New("models: no notification_preferences provided for upsert") + } + + if err := o.doBeforeUpsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(notificationPreferenceColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + notificationPreferenceUpsertCacheMut.RLock() + cache, cached := notificationPreferenceUpsertCache[key] + notificationPreferenceUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, ret := insertColumns.InsertColumnSet( + notificationPreferenceAllColumns, + notificationPreferenceColumnsWithDefault, + notificationPreferenceColumnsWithoutDefault, + nzDefaults, + ) + update := updateColumns.UpdateColumnSet( + notificationPreferenceAllColumns, + notificationPreferencePrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert notification_preferences, could not build update column list") + } + + conflict := conflictColumns + if len(conflict) == 0 { + conflict = make([]string, len(notificationPreferencePrimaryKeyColumns)) + copy(conflict, notificationPreferencePrimaryKeyColumns) + } + cache.query = buildUpsertQueryCockroachDB(dialect, "\"notification_preferences\"", updateOnConflict, ret, update, conflict, insert) + + cache.valueMapping, err = queries.BindMapping(notificationPreferenceType, notificationPreferenceMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(notificationPreferenceType, notificationPreferenceMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.DebugMode { + _, _ = fmt.Fprintln(boil.DebugWriter, cache.query) + _, _ = fmt.Fprintln(boil.DebugWriter, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if err == sql.ErrNoRows { + err = nil // CockcorachDB doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert notification_preferences") + } + + if !cached { + notificationPreferenceUpsertCacheMut.Lock() + notificationPreferenceUpsertCache[key] = cache + notificationPreferenceUpsertCacheMut.Unlock() + } + + return o.doAfterUpsertHooks(ctx, exec) +} diff --git a/internal/models/notification_targets.go b/internal/models/notification_targets.go new file mode 100644 index 0000000..9dc1fed --- /dev/null +++ b/internal/models/notification_targets.go @@ -0,0 +1,1268 @@ +// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// NotificationTarget is an object representing the database table. +type NotificationTarget struct { + ID string `boil:"id" json:"id" toml:"id" yaml:"id"` + Name string `boil:"name" json:"name" toml:"name" yaml:"name"` + Slug string `boil:"slug" json:"slug" toml:"slug" yaml:"slug"` + Description string `boil:"description" json:"description" toml:"description" yaml:"description"` + DefaultEnabled bool `boil:"default_enabled" json:"default_enabled" toml:"default_enabled" yaml:"default_enabled"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + DeletedAt null.Time `boil:"deleted_at" json:"deleted_at,omitempty" toml:"deleted_at" yaml:"deleted_at,omitempty"` + + R *notificationTargetR `boil:"-" json:"-" toml:"-" yaml:"-"` + L notificationTargetL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var NotificationTargetColumns = struct { + ID string + Name string + Slug string + Description string + DefaultEnabled string + CreatedAt string + UpdatedAt string + DeletedAt string +}{ + ID: "id", + Name: "name", + Slug: "slug", + Description: "description", + DefaultEnabled: "default_enabled", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +var NotificationTargetTableColumns = struct { + ID string + Name string + Slug string + Description string + DefaultEnabled string + CreatedAt string + UpdatedAt string + DeletedAt string +}{ + ID: "notification_targets.id", + Name: "notification_targets.name", + Slug: "notification_targets.slug", + Description: "notification_targets.description", + DefaultEnabled: "notification_targets.default_enabled", + CreatedAt: "notification_targets.created_at", + UpdatedAt: "notification_targets.updated_at", + DeletedAt: "notification_targets.deleted_at", +} + +// Generated where + +var NotificationTargetWhere = struct { + ID whereHelperstring + Name whereHelperstring + Slug whereHelperstring + Description whereHelperstring + DefaultEnabled whereHelperbool + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time + DeletedAt whereHelpernull_Time +}{ + ID: whereHelperstring{field: "\"notification_targets\".\"id\""}, + Name: whereHelperstring{field: "\"notification_targets\".\"name\""}, + Slug: whereHelperstring{field: "\"notification_targets\".\"slug\""}, + Description: whereHelperstring{field: "\"notification_targets\".\"description\""}, + DefaultEnabled: whereHelperbool{field: "\"notification_targets\".\"default_enabled\""}, + CreatedAt: whereHelpertime_Time{field: "\"notification_targets\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"notification_targets\".\"updated_at\""}, + DeletedAt: whereHelpernull_Time{field: "\"notification_targets\".\"deleted_at\""}, +} + +// NotificationTargetRels is where relationship names are stored. +var NotificationTargetRels = struct { + NotificationPreferences string +}{ + NotificationPreferences: "NotificationPreferences", +} + +// notificationTargetR is where relationships are stored. +type notificationTargetR struct { + NotificationPreferences NotificationPreferenceSlice `boil:"NotificationPreferences" json:"NotificationPreferences" toml:"NotificationPreferences" yaml:"NotificationPreferences"` +} + +// NewStruct creates a new relationship struct +func (*notificationTargetR) NewStruct() *notificationTargetR { + return ¬ificationTargetR{} +} + +func (r *notificationTargetR) GetNotificationPreferences() NotificationPreferenceSlice { + if r == nil { + return nil + } + return r.NotificationPreferences +} + +// notificationTargetL is where Load methods for each relationship are stored. +type notificationTargetL struct{} + +var ( + notificationTargetAllColumns = []string{"id", "name", "slug", "description", "default_enabled", "created_at", "updated_at", "deleted_at"} + notificationTargetColumnsWithoutDefault = []string{"name", "slug", "description", "default_enabled"} + notificationTargetColumnsWithDefault = []string{"id", "created_at", "updated_at", "deleted_at"} + notificationTargetPrimaryKeyColumns = []string{"id"} + notificationTargetGeneratedColumns = []string{} +) + +type ( + // NotificationTargetSlice is an alias for a slice of pointers to NotificationTarget. + // This should almost always be used instead of []NotificationTarget. + NotificationTargetSlice []*NotificationTarget + // NotificationTargetHook is the signature for custom NotificationTarget hook methods + NotificationTargetHook func(context.Context, boil.ContextExecutor, *NotificationTarget) error + + notificationTargetQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + notificationTargetType = reflect.TypeOf(&NotificationTarget{}) + notificationTargetMapping = queries.MakeStructMapping(notificationTargetType) + notificationTargetPrimaryKeyMapping, _ = queries.BindMapping(notificationTargetType, notificationTargetMapping, notificationTargetPrimaryKeyColumns) + notificationTargetInsertCacheMut sync.RWMutex + notificationTargetInsertCache = make(map[string]insertCache) + notificationTargetUpdateCacheMut sync.RWMutex + notificationTargetUpdateCache = make(map[string]updateCache) + notificationTargetUpsertCacheMut sync.RWMutex + notificationTargetUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +var notificationTargetAfterSelectHooks []NotificationTargetHook + +var notificationTargetBeforeInsertHooks []NotificationTargetHook +var notificationTargetAfterInsertHooks []NotificationTargetHook + +var notificationTargetBeforeUpdateHooks []NotificationTargetHook +var notificationTargetAfterUpdateHooks []NotificationTargetHook + +var notificationTargetBeforeDeleteHooks []NotificationTargetHook +var notificationTargetAfterDeleteHooks []NotificationTargetHook + +var notificationTargetBeforeUpsertHooks []NotificationTargetHook +var notificationTargetAfterUpsertHooks []NotificationTargetHook + +// doAfterSelectHooks executes all "after Select" hooks. +func (o *NotificationTarget) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTargetAfterSelectHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeInsertHooks executes all "before insert" hooks. +func (o *NotificationTarget) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTargetBeforeInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterInsertHooks executes all "after Insert" hooks. +func (o *NotificationTarget) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTargetAfterInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpdateHooks executes all "before Update" hooks. +func (o *NotificationTarget) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTargetBeforeUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpdateHooks executes all "after Update" hooks. +func (o *NotificationTarget) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTargetAfterUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeDeleteHooks executes all "before Delete" hooks. +func (o *NotificationTarget) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTargetBeforeDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterDeleteHooks executes all "after Delete" hooks. +func (o *NotificationTarget) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTargetAfterDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpsertHooks executes all "before Upsert" hooks. +func (o *NotificationTarget) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTargetBeforeUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpsertHooks executes all "after Upsert" hooks. +func (o *NotificationTarget) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTargetAfterUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// AddNotificationTargetHook registers your hook function for all future operations. +func AddNotificationTargetHook(hookPoint boil.HookPoint, notificationTargetHook NotificationTargetHook) { + switch hookPoint { + case boil.AfterSelectHook: + notificationTargetAfterSelectHooks = append(notificationTargetAfterSelectHooks, notificationTargetHook) + case boil.BeforeInsertHook: + notificationTargetBeforeInsertHooks = append(notificationTargetBeforeInsertHooks, notificationTargetHook) + case boil.AfterInsertHook: + notificationTargetAfterInsertHooks = append(notificationTargetAfterInsertHooks, notificationTargetHook) + case boil.BeforeUpdateHook: + notificationTargetBeforeUpdateHooks = append(notificationTargetBeforeUpdateHooks, notificationTargetHook) + case boil.AfterUpdateHook: + notificationTargetAfterUpdateHooks = append(notificationTargetAfterUpdateHooks, notificationTargetHook) + case boil.BeforeDeleteHook: + notificationTargetBeforeDeleteHooks = append(notificationTargetBeforeDeleteHooks, notificationTargetHook) + case boil.AfterDeleteHook: + notificationTargetAfterDeleteHooks = append(notificationTargetAfterDeleteHooks, notificationTargetHook) + case boil.BeforeUpsertHook: + notificationTargetBeforeUpsertHooks = append(notificationTargetBeforeUpsertHooks, notificationTargetHook) + case boil.AfterUpsertHook: + notificationTargetAfterUpsertHooks = append(notificationTargetAfterUpsertHooks, notificationTargetHook) + } +} + +// One returns a single notificationTarget record from the query. +func (q notificationTargetQuery) One(ctx context.Context, exec boil.ContextExecutor) (*NotificationTarget, error) { + o := &NotificationTarget{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for notification_targets") + } + + if err := o.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + + return o, nil +} + +// All returns all NotificationTarget records from the query. +func (q notificationTargetQuery) All(ctx context.Context, exec boil.ContextExecutor) (NotificationTargetSlice, error) { + var o []*NotificationTarget + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to NotificationTarget slice") + } + + if len(notificationTargetAfterSelectHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + } + } + + return o, nil +} + +// Count returns the count of all NotificationTarget records in the query. +func (q notificationTargetQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count notification_targets rows") + } + + return count, nil +} + +// Exists checks if the row exists in the table. +func (q notificationTargetQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if notification_targets exists") + } + + return count > 0, nil +} + +// NotificationPreferences retrieves all the notification_preference's NotificationPreferences with an executor. +func (o *NotificationTarget) NotificationPreferences(mods ...qm.QueryMod) notificationPreferenceQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) + } + + queryMods = append(queryMods, + qm.Where("\"notification_preferences\".\"notification_target_id\"=?", o.ID), + ) + + return NotificationPreferences(queryMods...) +} + +// LoadNotificationPreferences allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (notificationTargetL) LoadNotificationPreferences(ctx context.Context, e boil.ContextExecutor, singular bool, maybeNotificationTarget interface{}, mods queries.Applicator) error { + var slice []*NotificationTarget + var object *NotificationTarget + + if singular { + var ok bool + object, ok = maybeNotificationTarget.(*NotificationTarget) + if !ok { + object = new(NotificationTarget) + ok = queries.SetFromEmbeddedStruct(&object, &maybeNotificationTarget) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeNotificationTarget)) + } + } + } else { + s, ok := maybeNotificationTarget.(*[]*NotificationTarget) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeNotificationTarget) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeNotificationTarget)) + } + } + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = ¬ificationTargetR{} + } + args = append(args, object.ID) + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = ¬ificationTargetR{} + } + + for _, a := range args { + if queries.Equal(a, obj.ID) { + continue Outer + } + } + + args = append(args, obj.ID) + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery( + qm.From(`notification_preferences`), + qm.WhereIn(`notification_preferences.notification_target_id in ?`, args...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load notification_preferences") + } + + var resultSlice []*NotificationPreference + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice notification_preferences") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results in eager load on notification_preferences") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for notification_preferences") + } + + if len(notificationPreferenceAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + if singular { + object.R.NotificationPreferences = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = ¬ificationPreferenceR{} + } + foreign.R.NotificationTarget = object + } + return nil + } + + for _, foreign := range resultSlice { + for _, local := range slice { + if queries.Equal(local.ID, foreign.NotificationTargetID) { + local.R.NotificationPreferences = append(local.R.NotificationPreferences, foreign) + if foreign.R == nil { + foreign.R = ¬ificationPreferenceR{} + } + foreign.R.NotificationTarget = local + break + } + } + } + + return nil +} + +// AddNotificationPreferences adds the given related objects to the existing relationships +// of the notification_target, optionally inserting them as new records. +// Appends related to o.R.NotificationPreferences. +// Sets related.R.NotificationTarget appropriately. +func (o *NotificationTarget) AddNotificationPreferences(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*NotificationPreference) error { + var err error + for _, rel := range related { + if insert { + queries.Assign(&rel.NotificationTargetID, o.ID) + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } else { + updateQuery := fmt.Sprintf( + "UPDATE \"notification_preferences\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"notification_target_id"}), + strmangle.WhereClause("\"", "\"", 2, notificationPreferencePrimaryKeyColumns), + ) + values := []interface{}{o.ID, rel.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update foreign table") + } + + queries.Assign(&rel.NotificationTargetID, o.ID) + } + } + + if o.R == nil { + o.R = ¬ificationTargetR{ + NotificationPreferences: related, + } + } else { + o.R.NotificationPreferences = append(o.R.NotificationPreferences, related...) + } + + for _, rel := range related { + if rel.R == nil { + rel.R = ¬ificationPreferenceR{ + NotificationTarget: o, + } + } else { + rel.R.NotificationTarget = o + } + } + return nil +} + +// SetNotificationPreferences removes all previously related items of the +// notification_target replacing them completely with the passed +// in related items, optionally inserting them as new records. +// Sets o.R.NotificationTarget's NotificationPreferences accordingly. +// Replaces o.R.NotificationPreferences with related. +// Sets related.R.NotificationTarget's NotificationPreferences accordingly. +func (o *NotificationTarget) SetNotificationPreferences(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*NotificationPreference) error { + query := "update \"notification_preferences\" set \"notification_target_id\" = null where \"notification_target_id\" = $1" + values := []interface{}{o.ID} + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err := exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + + if o.R != nil { + for _, rel := range o.R.NotificationPreferences { + queries.SetScanner(&rel.NotificationTargetID, nil) + if rel.R == nil { + continue + } + + rel.R.NotificationTarget = nil + } + o.R.NotificationPreferences = nil + } + + return o.AddNotificationPreferences(ctx, exec, insert, related...) +} + +// RemoveNotificationPreferences relationships from objects passed in. +// Removes related items from R.NotificationPreferences (uses pointer comparison, removal does not keep order) +// Sets related.R.NotificationTarget. +func (o *NotificationTarget) RemoveNotificationPreferences(ctx context.Context, exec boil.ContextExecutor, related ...*NotificationPreference) error { + if len(related) == 0 { + return nil + } + + var err error + for _, rel := range related { + queries.SetScanner(&rel.NotificationTargetID, nil) + if rel.R != nil { + rel.R.NotificationTarget = nil + } + if _, err = rel.Update(ctx, exec, boil.Whitelist("notification_target_id")); err != nil { + return err + } + } + if o.R == nil { + return nil + } + + for _, rel := range related { + for i, ri := range o.R.NotificationPreferences { + if rel != ri { + continue + } + + ln := len(o.R.NotificationPreferences) + if ln > 1 && i < ln-1 { + o.R.NotificationPreferences[i] = o.R.NotificationPreferences[ln-1] + } + o.R.NotificationPreferences = o.R.NotificationPreferences[:ln-1] + break + } + } + + return nil +} + +// NotificationTargets retrieves all the records using an executor. +func NotificationTargets(mods ...qm.QueryMod) notificationTargetQuery { + mods = append(mods, qm.From("\"notification_targets\""), qmhelper.WhereIsNull("\"notification_targets\".\"deleted_at\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"notification_targets\".*"}) + } + + return notificationTargetQuery{q} +} + +// FindNotificationTarget retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindNotificationTarget(ctx context.Context, exec boil.ContextExecutor, iD string, selectCols ...string) (*NotificationTarget, error) { + notificationTargetObj := &NotificationTarget{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"notification_targets\" where \"id\"=$1 and \"deleted_at\" is null", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, notificationTargetObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from notification_targets") + } + + if err = notificationTargetObj.doAfterSelectHooks(ctx, exec); err != nil { + return notificationTargetObj, err + } + + return notificationTargetObj, nil +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *NotificationTarget) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no notification_targets provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + if err := o.doBeforeInsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(notificationTargetColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + notificationTargetInsertCacheMut.RLock() + cache, cached := notificationTargetInsertCache[key] + notificationTargetInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + notificationTargetAllColumns, + notificationTargetColumnsWithDefault, + notificationTargetColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(notificationTargetType, notificationTargetMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(notificationTargetType, notificationTargetMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"notification_targets\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"notification_targets\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into notification_targets") + } + + if !cached { + notificationTargetInsertCacheMut.Lock() + notificationTargetInsertCache[key] = cache + notificationTargetInsertCacheMut.Unlock() + } + + return o.doAfterInsertHooks(ctx, exec) +} + +// Update uses an executor to update the NotificationTarget. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *NotificationTarget) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + if err = o.doBeforeUpdateHooks(ctx, exec); err != nil { + return 0, err + } + key := makeCacheKey(columns, nil) + notificationTargetUpdateCacheMut.RLock() + cache, cached := notificationTargetUpdateCache[key] + notificationTargetUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + notificationTargetAllColumns, + notificationTargetPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update notification_targets, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"notification_targets\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, notificationTargetPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(notificationTargetType, notificationTargetMapping, append(wl, notificationTargetPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update notification_targets row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for notification_targets") + } + + if !cached { + notificationTargetUpdateCacheMut.Lock() + notificationTargetUpdateCache[key] = cache + notificationTargetUpdateCacheMut.Unlock() + } + + return rowsAff, o.doAfterUpdateHooks(ctx, exec) +} + +// UpdateAll updates all rows with the specified column values. +func (q notificationTargetQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for notification_targets") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for notification_targets") + } + + return rowsAff, nil +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o NotificationTargetSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationTargetPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"notification_targets\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, notificationTargetPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in notificationTarget slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all notificationTarget") + } + return rowsAff, nil +} + +// Delete deletes a single NotificationTarget record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *NotificationTarget) Delete(ctx context.Context, exec boil.ContextExecutor, hardDelete bool) (int64, error) { + if o == nil { + return 0, errors.New("models: no NotificationTarget provided for delete") + } + + if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + var ( + sql string + args []interface{} + ) + if hardDelete { + args = queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), notificationTargetPrimaryKeyMapping) + sql = "DELETE FROM \"notification_targets\" WHERE \"id\"=$1" + } else { + currTime := time.Now().In(boil.GetLocation()) + o.DeletedAt = null.TimeFrom(currTime) + wl := []string{"deleted_at"} + sql = fmt.Sprintf("UPDATE \"notification_targets\" SET %s WHERE \"id\"=$2", + strmangle.SetParamNames("\"", "\"", 1, wl), + ) + valueMapping, err := queries.BindMapping(notificationTargetType, notificationTargetMapping, append(wl, notificationTargetPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + args = queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), valueMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from notification_targets") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for notification_targets") + } + + if err := o.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + return rowsAff, nil +} + +// DeleteAll deletes all matching rows. +func (q notificationTargetQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor, hardDelete bool) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no notificationTargetQuery provided for delete all") + } + + if hardDelete { + queries.SetDelete(q.Query) + } else { + currTime := time.Now().In(boil.GetLocation()) + queries.SetUpdate(q.Query, M{"deleted_at": currTime}) + } + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from notification_targets") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for notification_targets") + } + + return rowsAff, nil +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o NotificationTargetSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor, hardDelete bool) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + if len(notificationTargetBeforeDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + var ( + sql string + args []interface{} + ) + if hardDelete { + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationTargetPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + sql = "DELETE FROM \"notification_targets\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, notificationTargetPrimaryKeyColumns, len(o)) + } else { + currTime := time.Now().In(boil.GetLocation()) + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationTargetPrimaryKeyMapping) + args = append(args, pkeyArgs...) + obj.DeletedAt = null.TimeFrom(currTime) + } + wl := []string{"deleted_at"} + sql = fmt.Sprintf("UPDATE \"notification_targets\" SET %s WHERE "+ + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 2, notificationTargetPrimaryKeyColumns, len(o)), + strmangle.SetParamNames("\"", "\"", 1, wl), + ) + args = append([]interface{}{currTime}, args...) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from notificationTarget slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for notification_targets") + } + + if len(notificationTargetAfterDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + return rowsAff, nil +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *NotificationTarget) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindNotificationTarget(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *NotificationTargetSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := NotificationTargetSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationTargetPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"notification_targets\".* FROM \"notification_targets\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, notificationTargetPrimaryKeyColumns, len(*o)) + + "and \"deleted_at\" is null" + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in NotificationTargetSlice") + } + + *o = slice + + return nil +} + +// NotificationTargetExists checks if the NotificationTarget row exists. +func NotificationTargetExists(ctx context.Context, exec boil.ContextExecutor, iD string) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"notification_targets\" where \"id\"=$1 and \"deleted_at\" is null limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if notification_targets exists") + } + + return exists, nil +} + +// Exists checks if the NotificationTarget row exists. +func (o *NotificationTarget) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return NotificationTargetExists(ctx, exec, o.ID) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *NotificationTarget) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns) error { + if o == nil { + return errors.New("models: no notification_targets provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + if err := o.doBeforeUpsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(notificationTargetColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + notificationTargetUpsertCacheMut.RLock() + cache, cached := notificationTargetUpsertCache[key] + notificationTargetUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, ret := insertColumns.InsertColumnSet( + notificationTargetAllColumns, + notificationTargetColumnsWithDefault, + notificationTargetColumnsWithoutDefault, + nzDefaults, + ) + update := updateColumns.UpdateColumnSet( + notificationTargetAllColumns, + notificationTargetPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert notification_targets, could not build update column list") + } + + conflict := conflictColumns + if len(conflict) == 0 { + conflict = make([]string, len(notificationTargetPrimaryKeyColumns)) + copy(conflict, notificationTargetPrimaryKeyColumns) + } + cache.query = buildUpsertQueryCockroachDB(dialect, "\"notification_targets\"", updateOnConflict, ret, update, conflict, insert) + + cache.valueMapping, err = queries.BindMapping(notificationTargetType, notificationTargetMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(notificationTargetType, notificationTargetMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.DebugMode { + _, _ = fmt.Fprintln(boil.DebugWriter, cache.query) + _, _ = fmt.Fprintln(boil.DebugWriter, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if err == sql.ErrNoRows { + err = nil // CockcorachDB doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert notification_targets") + } + + if !cached { + notificationTargetUpsertCacheMut.Lock() + notificationTargetUpsertCache[key] = cache + notificationTargetUpsertCacheMut.Unlock() + } + + return o.doAfterUpsertHooks(ctx, exec) +} diff --git a/internal/models/notification_types.go b/internal/models/notification_types.go new file mode 100644 index 0000000..219d453 --- /dev/null +++ b/internal/models/notification_types.go @@ -0,0 +1,1194 @@ +// Code generated by SQLBoiler 4.14.2 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// NotificationType is an object representing the database table. +type NotificationType struct { + ID string `boil:"id" json:"id" toml:"id" yaml:"id"` + Name string `boil:"name" json:"name" toml:"name" yaml:"name"` + Slug string `boil:"slug" json:"slug" toml:"slug" yaml:"slug"` + Description string `boil:"description" json:"description" toml:"description" yaml:"description"` + DefaultEnabled bool `boil:"default_enabled" json:"default_enabled" toml:"default_enabled" yaml:"default_enabled"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + DeletedAt null.Time `boil:"deleted_at" json:"deleted_at,omitempty" toml:"deleted_at" yaml:"deleted_at,omitempty"` + + R *notificationTypeR `boil:"-" json:"-" toml:"-" yaml:"-"` + L notificationTypeL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var NotificationTypeColumns = struct { + ID string + Name string + Slug string + Description string + DefaultEnabled string + CreatedAt string + UpdatedAt string + DeletedAt string +}{ + ID: "id", + Name: "name", + Slug: "slug", + Description: "description", + DefaultEnabled: "default_enabled", + CreatedAt: "created_at", + UpdatedAt: "updated_at", + DeletedAt: "deleted_at", +} + +var NotificationTypeTableColumns = struct { + ID string + Name string + Slug string + Description string + DefaultEnabled string + CreatedAt string + UpdatedAt string + DeletedAt string +}{ + ID: "notification_types.id", + Name: "notification_types.name", + Slug: "notification_types.slug", + Description: "notification_types.description", + DefaultEnabled: "notification_types.default_enabled", + CreatedAt: "notification_types.created_at", + UpdatedAt: "notification_types.updated_at", + DeletedAt: "notification_types.deleted_at", +} + +// Generated where + +var NotificationTypeWhere = struct { + ID whereHelperstring + Name whereHelperstring + Slug whereHelperstring + Description whereHelperstring + DefaultEnabled whereHelperbool + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time + DeletedAt whereHelpernull_Time +}{ + ID: whereHelperstring{field: "\"notification_types\".\"id\""}, + Name: whereHelperstring{field: "\"notification_types\".\"name\""}, + Slug: whereHelperstring{field: "\"notification_types\".\"slug\""}, + Description: whereHelperstring{field: "\"notification_types\".\"description\""}, + DefaultEnabled: whereHelperbool{field: "\"notification_types\".\"default_enabled\""}, + CreatedAt: whereHelpertime_Time{field: "\"notification_types\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"notification_types\".\"updated_at\""}, + DeletedAt: whereHelpernull_Time{field: "\"notification_types\".\"deleted_at\""}, +} + +// NotificationTypeRels is where relationship names are stored. +var NotificationTypeRels = struct { + NotificationPreferences string +}{ + NotificationPreferences: "NotificationPreferences", +} + +// notificationTypeR is where relationships are stored. +type notificationTypeR struct { + NotificationPreferences NotificationPreferenceSlice `boil:"NotificationPreferences" json:"NotificationPreferences" toml:"NotificationPreferences" yaml:"NotificationPreferences"` +} + +// NewStruct creates a new relationship struct +func (*notificationTypeR) NewStruct() *notificationTypeR { + return ¬ificationTypeR{} +} + +func (r *notificationTypeR) GetNotificationPreferences() NotificationPreferenceSlice { + if r == nil { + return nil + } + return r.NotificationPreferences +} + +// notificationTypeL is where Load methods for each relationship are stored. +type notificationTypeL struct{} + +var ( + notificationTypeAllColumns = []string{"id", "name", "slug", "description", "default_enabled", "created_at", "updated_at", "deleted_at"} + notificationTypeColumnsWithoutDefault = []string{"name", "slug", "description", "default_enabled"} + notificationTypeColumnsWithDefault = []string{"id", "created_at", "updated_at", "deleted_at"} + notificationTypePrimaryKeyColumns = []string{"id"} + notificationTypeGeneratedColumns = []string{} +) + +type ( + // NotificationTypeSlice is an alias for a slice of pointers to NotificationType. + // This should almost always be used instead of []NotificationType. + NotificationTypeSlice []*NotificationType + // NotificationTypeHook is the signature for custom NotificationType hook methods + NotificationTypeHook func(context.Context, boil.ContextExecutor, *NotificationType) error + + notificationTypeQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + notificationTypeType = reflect.TypeOf(&NotificationType{}) + notificationTypeMapping = queries.MakeStructMapping(notificationTypeType) + notificationTypePrimaryKeyMapping, _ = queries.BindMapping(notificationTypeType, notificationTypeMapping, notificationTypePrimaryKeyColumns) + notificationTypeInsertCacheMut sync.RWMutex + notificationTypeInsertCache = make(map[string]insertCache) + notificationTypeUpdateCacheMut sync.RWMutex + notificationTypeUpdateCache = make(map[string]updateCache) + notificationTypeUpsertCacheMut sync.RWMutex + notificationTypeUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +var notificationTypeAfterSelectHooks []NotificationTypeHook + +var notificationTypeBeforeInsertHooks []NotificationTypeHook +var notificationTypeAfterInsertHooks []NotificationTypeHook + +var notificationTypeBeforeUpdateHooks []NotificationTypeHook +var notificationTypeAfterUpdateHooks []NotificationTypeHook + +var notificationTypeBeforeDeleteHooks []NotificationTypeHook +var notificationTypeAfterDeleteHooks []NotificationTypeHook + +var notificationTypeBeforeUpsertHooks []NotificationTypeHook +var notificationTypeAfterUpsertHooks []NotificationTypeHook + +// doAfterSelectHooks executes all "after Select" hooks. +func (o *NotificationType) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTypeAfterSelectHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeInsertHooks executes all "before insert" hooks. +func (o *NotificationType) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTypeBeforeInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterInsertHooks executes all "after Insert" hooks. +func (o *NotificationType) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTypeAfterInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpdateHooks executes all "before Update" hooks. +func (o *NotificationType) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTypeBeforeUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpdateHooks executes all "after Update" hooks. +func (o *NotificationType) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTypeAfterUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeDeleteHooks executes all "before Delete" hooks. +func (o *NotificationType) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTypeBeforeDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterDeleteHooks executes all "after Delete" hooks. +func (o *NotificationType) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTypeAfterDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpsertHooks executes all "before Upsert" hooks. +func (o *NotificationType) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTypeBeforeUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpsertHooks executes all "after Upsert" hooks. +func (o *NotificationType) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range notificationTypeAfterUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// AddNotificationTypeHook registers your hook function for all future operations. +func AddNotificationTypeHook(hookPoint boil.HookPoint, notificationTypeHook NotificationTypeHook) { + switch hookPoint { + case boil.AfterSelectHook: + notificationTypeAfterSelectHooks = append(notificationTypeAfterSelectHooks, notificationTypeHook) + case boil.BeforeInsertHook: + notificationTypeBeforeInsertHooks = append(notificationTypeBeforeInsertHooks, notificationTypeHook) + case boil.AfterInsertHook: + notificationTypeAfterInsertHooks = append(notificationTypeAfterInsertHooks, notificationTypeHook) + case boil.BeforeUpdateHook: + notificationTypeBeforeUpdateHooks = append(notificationTypeBeforeUpdateHooks, notificationTypeHook) + case boil.AfterUpdateHook: + notificationTypeAfterUpdateHooks = append(notificationTypeAfterUpdateHooks, notificationTypeHook) + case boil.BeforeDeleteHook: + notificationTypeBeforeDeleteHooks = append(notificationTypeBeforeDeleteHooks, notificationTypeHook) + case boil.AfterDeleteHook: + notificationTypeAfterDeleteHooks = append(notificationTypeAfterDeleteHooks, notificationTypeHook) + case boil.BeforeUpsertHook: + notificationTypeBeforeUpsertHooks = append(notificationTypeBeforeUpsertHooks, notificationTypeHook) + case boil.AfterUpsertHook: + notificationTypeAfterUpsertHooks = append(notificationTypeAfterUpsertHooks, notificationTypeHook) + } +} + +// One returns a single notificationType record from the query. +func (q notificationTypeQuery) One(ctx context.Context, exec boil.ContextExecutor) (*NotificationType, error) { + o := &NotificationType{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for notification_types") + } + + if err := o.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + + return o, nil +} + +// All returns all NotificationType records from the query. +func (q notificationTypeQuery) All(ctx context.Context, exec boil.ContextExecutor) (NotificationTypeSlice, error) { + var o []*NotificationType + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to NotificationType slice") + } + + if len(notificationTypeAfterSelectHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + } + } + + return o, nil +} + +// Count returns the count of all NotificationType records in the query. +func (q notificationTypeQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count notification_types rows") + } + + return count, nil +} + +// Exists checks if the row exists in the table. +func (q notificationTypeQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if notification_types exists") + } + + return count > 0, nil +} + +// NotificationPreferences retrieves all the notification_preference's NotificationPreferences with an executor. +func (o *NotificationType) NotificationPreferences(mods ...qm.QueryMod) notificationPreferenceQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) + } + + queryMods = append(queryMods, + qm.Where("\"notification_preferences\".\"notification_type_id\"=?", o.ID), + ) + + return NotificationPreferences(queryMods...) +} + +// LoadNotificationPreferences allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (notificationTypeL) LoadNotificationPreferences(ctx context.Context, e boil.ContextExecutor, singular bool, maybeNotificationType interface{}, mods queries.Applicator) error { + var slice []*NotificationType + var object *NotificationType + + if singular { + var ok bool + object, ok = maybeNotificationType.(*NotificationType) + if !ok { + object = new(NotificationType) + ok = queries.SetFromEmbeddedStruct(&object, &maybeNotificationType) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeNotificationType)) + } + } + } else { + s, ok := maybeNotificationType.(*[]*NotificationType) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeNotificationType) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeNotificationType)) + } + } + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = ¬ificationTypeR{} + } + args = append(args, object.ID) + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = ¬ificationTypeR{} + } + + for _, a := range args { + if a == obj.ID { + continue Outer + } + } + + args = append(args, obj.ID) + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery( + qm.From(`notification_preferences`), + qm.WhereIn(`notification_preferences.notification_type_id in ?`, args...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load notification_preferences") + } + + var resultSlice []*NotificationPreference + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice notification_preferences") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results in eager load on notification_preferences") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for notification_preferences") + } + + if len(notificationPreferenceAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + if singular { + object.R.NotificationPreferences = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = ¬ificationPreferenceR{} + } + foreign.R.NotificationType = object + } + return nil + } + + for _, foreign := range resultSlice { + for _, local := range slice { + if local.ID == foreign.NotificationTypeID { + local.R.NotificationPreferences = append(local.R.NotificationPreferences, foreign) + if foreign.R == nil { + foreign.R = ¬ificationPreferenceR{} + } + foreign.R.NotificationType = local + break + } + } + } + + return nil +} + +// AddNotificationPreferences adds the given related objects to the existing relationships +// of the notification_type, optionally inserting them as new records. +// Appends related to o.R.NotificationPreferences. +// Sets related.R.NotificationType appropriately. +func (o *NotificationType) AddNotificationPreferences(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*NotificationPreference) error { + var err error + for _, rel := range related { + if insert { + rel.NotificationTypeID = o.ID + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } else { + updateQuery := fmt.Sprintf( + "UPDATE \"notification_preferences\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"notification_type_id"}), + strmangle.WhereClause("\"", "\"", 2, notificationPreferencePrimaryKeyColumns), + ) + values := []interface{}{o.ID, rel.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update foreign table") + } + + rel.NotificationTypeID = o.ID + } + } + + if o.R == nil { + o.R = ¬ificationTypeR{ + NotificationPreferences: related, + } + } else { + o.R.NotificationPreferences = append(o.R.NotificationPreferences, related...) + } + + for _, rel := range related { + if rel.R == nil { + rel.R = ¬ificationPreferenceR{ + NotificationType: o, + } + } else { + rel.R.NotificationType = o + } + } + return nil +} + +// NotificationTypes retrieves all the records using an executor. +func NotificationTypes(mods ...qm.QueryMod) notificationTypeQuery { + mods = append(mods, qm.From("\"notification_types\""), qmhelper.WhereIsNull("\"notification_types\".\"deleted_at\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"notification_types\".*"}) + } + + return notificationTypeQuery{q} +} + +// FindNotificationType retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindNotificationType(ctx context.Context, exec boil.ContextExecutor, iD string, selectCols ...string) (*NotificationType, error) { + notificationTypeObj := &NotificationType{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"notification_types\" where \"id\"=$1 and \"deleted_at\" is null", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, notificationTypeObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from notification_types") + } + + if err = notificationTypeObj.doAfterSelectHooks(ctx, exec); err != nil { + return notificationTypeObj, err + } + + return notificationTypeObj, nil +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *NotificationType) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no notification_types provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + if err := o.doBeforeInsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(notificationTypeColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + notificationTypeInsertCacheMut.RLock() + cache, cached := notificationTypeInsertCache[key] + notificationTypeInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + notificationTypeAllColumns, + notificationTypeColumnsWithDefault, + notificationTypeColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(notificationTypeType, notificationTypeMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(notificationTypeType, notificationTypeMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"notification_types\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"notification_types\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into notification_types") + } + + if !cached { + notificationTypeInsertCacheMut.Lock() + notificationTypeInsertCache[key] = cache + notificationTypeInsertCacheMut.Unlock() + } + + return o.doAfterInsertHooks(ctx, exec) +} + +// Update uses an executor to update the NotificationType. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *NotificationType) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + if err = o.doBeforeUpdateHooks(ctx, exec); err != nil { + return 0, err + } + key := makeCacheKey(columns, nil) + notificationTypeUpdateCacheMut.RLock() + cache, cached := notificationTypeUpdateCache[key] + notificationTypeUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + notificationTypeAllColumns, + notificationTypePrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update notification_types, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"notification_types\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, notificationTypePrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(notificationTypeType, notificationTypeMapping, append(wl, notificationTypePrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update notification_types row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for notification_types") + } + + if !cached { + notificationTypeUpdateCacheMut.Lock() + notificationTypeUpdateCache[key] = cache + notificationTypeUpdateCacheMut.Unlock() + } + + return rowsAff, o.doAfterUpdateHooks(ctx, exec) +} + +// UpdateAll updates all rows with the specified column values. +func (q notificationTypeQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for notification_types") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for notification_types") + } + + return rowsAff, nil +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o NotificationTypeSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationTypePrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"notification_types\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, notificationTypePrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in notificationType slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all notificationType") + } + return rowsAff, nil +} + +// Delete deletes a single NotificationType record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *NotificationType) Delete(ctx context.Context, exec boil.ContextExecutor, hardDelete bool) (int64, error) { + if o == nil { + return 0, errors.New("models: no NotificationType provided for delete") + } + + if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + var ( + sql string + args []interface{} + ) + if hardDelete { + args = queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), notificationTypePrimaryKeyMapping) + sql = "DELETE FROM \"notification_types\" WHERE \"id\"=$1" + } else { + currTime := time.Now().In(boil.GetLocation()) + o.DeletedAt = null.TimeFrom(currTime) + wl := []string{"deleted_at"} + sql = fmt.Sprintf("UPDATE \"notification_types\" SET %s WHERE \"id\"=$2", + strmangle.SetParamNames("\"", "\"", 1, wl), + ) + valueMapping, err := queries.BindMapping(notificationTypeType, notificationTypeMapping, append(wl, notificationTypePrimaryKeyColumns...)) + if err != nil { + return 0, err + } + args = queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), valueMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from notification_types") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for notification_types") + } + + if err := o.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + return rowsAff, nil +} + +// DeleteAll deletes all matching rows. +func (q notificationTypeQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor, hardDelete bool) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no notificationTypeQuery provided for delete all") + } + + if hardDelete { + queries.SetDelete(q.Query) + } else { + currTime := time.Now().In(boil.GetLocation()) + queries.SetUpdate(q.Query, M{"deleted_at": currTime}) + } + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from notification_types") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for notification_types") + } + + return rowsAff, nil +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o NotificationTypeSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor, hardDelete bool) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + if len(notificationTypeBeforeDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + var ( + sql string + args []interface{} + ) + if hardDelete { + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationTypePrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + sql = "DELETE FROM \"notification_types\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, notificationTypePrimaryKeyColumns, len(o)) + } else { + currTime := time.Now().In(boil.GetLocation()) + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationTypePrimaryKeyMapping) + args = append(args, pkeyArgs...) + obj.DeletedAt = null.TimeFrom(currTime) + } + wl := []string{"deleted_at"} + sql = fmt.Sprintf("UPDATE \"notification_types\" SET %s WHERE "+ + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 2, notificationTypePrimaryKeyColumns, len(o)), + strmangle.SetParamNames("\"", "\"", 1, wl), + ) + args = append([]interface{}{currTime}, args...) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from notificationType slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for notification_types") + } + + if len(notificationTypeAfterDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + return rowsAff, nil +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *NotificationType) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindNotificationType(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *NotificationTypeSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := NotificationTypeSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), notificationTypePrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"notification_types\".* FROM \"notification_types\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, notificationTypePrimaryKeyColumns, len(*o)) + + "and \"deleted_at\" is null" + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in NotificationTypeSlice") + } + + *o = slice + + return nil +} + +// NotificationTypeExists checks if the NotificationType row exists. +func NotificationTypeExists(ctx context.Context, exec boil.ContextExecutor, iD string) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"notification_types\" where \"id\"=$1 and \"deleted_at\" is null limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if notification_types exists") + } + + return exists, nil +} + +// Exists checks if the NotificationType row exists. +func (o *NotificationType) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return NotificationTypeExists(ctx, exec, o.ID) +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *NotificationType) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns) error { + if o == nil { + return errors.New("models: no notification_types provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + if err := o.doBeforeUpsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(notificationTypeColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + notificationTypeUpsertCacheMut.RLock() + cache, cached := notificationTypeUpsertCache[key] + notificationTypeUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, ret := insertColumns.InsertColumnSet( + notificationTypeAllColumns, + notificationTypeColumnsWithDefault, + notificationTypeColumnsWithoutDefault, + nzDefaults, + ) + update := updateColumns.UpdateColumnSet( + notificationTypeAllColumns, + notificationTypePrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert notification_types, could not build update column list") + } + + conflict := conflictColumns + if len(conflict) == 0 { + conflict = make([]string, len(notificationTypePrimaryKeyColumns)) + copy(conflict, notificationTypePrimaryKeyColumns) + } + cache.query = buildUpsertQueryCockroachDB(dialect, "\"notification_types\"", updateOnConflict, ret, update, conflict, insert) + + cache.valueMapping, err = queries.BindMapping(notificationTypeType, notificationTypeMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(notificationTypeType, notificationTypeMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.DebugMode { + _, _ = fmt.Fprintln(boil.DebugWriter, cache.query) + _, _ = fmt.Fprintln(boil.DebugWriter, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if err == sql.ErrNoRows { + err = nil // CockcorachDB doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert notification_types") + } + + if !cached { + notificationTypeUpsertCacheMut.Lock() + notificationTypeUpsertCache[key] = cache + notificationTypeUpsertCacheMut.Unlock() + } + + return o.doAfterUpsertHooks(ctx, exec) +} diff --git a/internal/models/users.go b/internal/models/users.go index 818889e..2003e00 100644 --- a/internal/models/users.go +++ b/internal/models/users.go @@ -202,12 +202,14 @@ var UserRels = struct { RequesterUserGroupApplicationRequests string GroupMembershipRequests string GroupMemberships string + NotificationPreferences string }{ SubjectUserAuditEvents: "SubjectUserAuditEvents", ActorAuditEvents: "ActorAuditEvents", RequesterUserGroupApplicationRequests: "RequesterUserGroupApplicationRequests", GroupMembershipRequests: "GroupMembershipRequests", GroupMemberships: "GroupMemberships", + NotificationPreferences: "NotificationPreferences", } // userR is where relationships are stored. @@ -217,6 +219,7 @@ type userR struct { RequesterUserGroupApplicationRequests GroupApplicationRequestSlice `boil:"RequesterUserGroupApplicationRequests" json:"RequesterUserGroupApplicationRequests" toml:"RequesterUserGroupApplicationRequests" yaml:"RequesterUserGroupApplicationRequests"` GroupMembershipRequests GroupMembershipRequestSlice `boil:"GroupMembershipRequests" json:"GroupMembershipRequests" toml:"GroupMembershipRequests" yaml:"GroupMembershipRequests"` GroupMemberships GroupMembershipSlice `boil:"GroupMemberships" json:"GroupMemberships" toml:"GroupMemberships" yaml:"GroupMemberships"` + NotificationPreferences NotificationPreferenceSlice `boil:"NotificationPreferences" json:"NotificationPreferences" toml:"NotificationPreferences" yaml:"NotificationPreferences"` } // NewStruct creates a new relationship struct @@ -259,6 +262,13 @@ func (r *userR) GetGroupMemberships() GroupMembershipSlice { return r.GroupMemberships } +func (r *userR) GetNotificationPreferences() NotificationPreferenceSlice { + if r == nil { + return nil + } + return r.NotificationPreferences +} + // userL is where Load methods for each relationship are stored. type userL struct{} @@ -618,6 +628,20 @@ func (o *User) GroupMemberships(mods ...qm.QueryMod) groupMembershipQuery { return GroupMemberships(queryMods...) } +// NotificationPreferences retrieves all the notification_preference's NotificationPreferences with an executor. +func (o *User) NotificationPreferences(mods ...qm.QueryMod) notificationPreferenceQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) + } + + queryMods = append(queryMods, + qm.Where("\"notification_preferences\".\"user_id\"=?", o.ID), + ) + + return NotificationPreferences(queryMods...) +} + // LoadSubjectUserAuditEvents allows an eager lookup of values, cached into the // loaded structs of the objects. This is for a 1-M or N-M relationship. func (userL) LoadSubjectUserAuditEvents(ctx context.Context, e boil.ContextExecutor, singular bool, maybeUser interface{}, mods queries.Applicator) error { @@ -1188,6 +1212,120 @@ func (userL) LoadGroupMemberships(ctx context.Context, e boil.ContextExecutor, s return nil } +// LoadNotificationPreferences allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (userL) LoadNotificationPreferences(ctx context.Context, e boil.ContextExecutor, singular bool, maybeUser interface{}, mods queries.Applicator) error { + var slice []*User + var object *User + + if singular { + var ok bool + object, ok = maybeUser.(*User) + if !ok { + object = new(User) + ok = queries.SetFromEmbeddedStruct(&object, &maybeUser) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeUser)) + } + } + } else { + s, ok := maybeUser.(*[]*User) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeUser) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeUser)) + } + } + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = &userR{} + } + args = append(args, object.ID) + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = &userR{} + } + + for _, a := range args { + if a == obj.ID { + continue Outer + } + } + + args = append(args, obj.ID) + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery( + qm.From(`notification_preferences`), + qm.WhereIn(`notification_preferences.user_id in ?`, args...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load notification_preferences") + } + + var resultSlice []*NotificationPreference + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice notification_preferences") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results in eager load on notification_preferences") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for notification_preferences") + } + + if len(notificationPreferenceAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + if singular { + object.R.NotificationPreferences = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = ¬ificationPreferenceR{} + } + foreign.R.User = object + } + return nil + } + + for _, foreign := range resultSlice { + for _, local := range slice { + if local.ID == foreign.UserID { + local.R.NotificationPreferences = append(local.R.NotificationPreferences, foreign) + if foreign.R == nil { + foreign.R = ¬ificationPreferenceR{} + } + foreign.R.User = local + break + } + } + } + + return nil +} + // AddSubjectUserAuditEvents adds the given related objects to the existing relationships // of the user, optionally inserting them as new records. // Appends related to o.R.SubjectUserAuditEvents. @@ -1601,6 +1739,59 @@ func (o *User) AddGroupMemberships(ctx context.Context, exec boil.ContextExecuto return nil } +// AddNotificationPreferences adds the given related objects to the existing relationships +// of the user, optionally inserting them as new records. +// Appends related to o.R.NotificationPreferences. +// Sets related.R.User appropriately. +func (o *User) AddNotificationPreferences(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*NotificationPreference) error { + var err error + for _, rel := range related { + if insert { + rel.UserID = o.ID + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } else { + updateQuery := fmt.Sprintf( + "UPDATE \"notification_preferences\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"user_id"}), + strmangle.WhereClause("\"", "\"", 2, notificationPreferencePrimaryKeyColumns), + ) + values := []interface{}{o.ID, rel.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update foreign table") + } + + rel.UserID = o.ID + } + } + + if o.R == nil { + o.R = &userR{ + NotificationPreferences: related, + } + } else { + o.R.NotificationPreferences = append(o.R.NotificationPreferences, related...) + } + + for _, rel := range related { + if rel.R == nil { + rel.R = ¬ificationPreferenceR{ + User: o, + } + } else { + rel.R.User = o + } + } + return nil +} + // Users retrieves all the records using an executor. func Users(mods ...qm.QueryMod) userQuery { mods = append(mods, qm.From("\"users\""), qmhelper.WhereIsNull("\"users\".\"deleted_at\"")) diff --git a/pkg/api/v1alpha1/authenticated_user.go b/pkg/api/v1alpha1/authenticated_user.go index a91c19c..ae39556 100644 --- a/pkg/api/v1alpha1/authenticated_user.go +++ b/pkg/api/v1alpha1/authenticated_user.go @@ -25,8 +25,9 @@ type AuthenticatedUser struct { // AuthenticatedUserReq is an authenticated user request payload for updating selected details type AuthenticatedUserReq struct { - AvatarURL *string `json:"avatar_url"` - GithubUsername *string `json:"github_username"` + AvatarURL *string `json:"avatar_url"` + GithubUsername *string `json:"github_username"` + NotificationPreferences UserNotificationPreferences `json:"notification_preferences,omitempty"` } // AuthenticatedUserGroup is an authenticated user group response @@ -72,13 +73,20 @@ func (r *Router) getAuthenticatedUser(c *gin.Context) { return } + notificationPreferences, err := dbtools.GetNotificationPreferences(c.Request.Context(), ctxUser.ID, r.DB, true) + if err != nil { + sendError(c, http.StatusInternalServerError, "error getting notification preferences: "+err.Error()) + return + } + if ctxUser.R == nil { c.JSON(http.StatusOK, AuthenticatedUser{ User: &User{ - User: ctxUser, - Memberships: []string{}, - MembershipsDirect: []string{}, - MembershipRequests: []string{}, + User: ctxUser, + Memberships: []string{}, + MembershipsDirect: []string{}, + MembershipRequests: []string{}, + NotificationPreferences: notificationPreferences, }, Admin: *ctxAdmin, }) @@ -111,10 +119,11 @@ func (r *Router) getAuthenticatedUser(c *gin.Context) { c.JSON(http.StatusOK, AuthenticatedUser{ User: &User{ - User: ctxUser, - Memberships: memberships, - MembershipsDirect: membershipsDirect, - MembershipRequests: requests, + User: ctxUser, + Memberships: memberships, + MembershipsDirect: membershipsDirect, + MembershipRequests: requests, + NotificationPreferences: notificationPreferences, }, Admin: *ctxAdmin, }) @@ -555,6 +564,22 @@ func (r *Router) updateAuthenticatedUser(c *gin.Context) { return } + if len(req.NotificationPreferences) > 0 { + if _, status, err := handleUpdateNotificationPreferencesRequests( + c, tx, ctxUser, req.NotificationPreferences, + ); err != nil { + msg := err.Error() + + if err := tx.Rollback(); err != nil { + msg += "error rolling back transaction: " + err.Error() + } + + sendError(c, status, msg) + + return + } + } + if err := tx.Commit(); err != nil { msg := "error committing user update, rolling back: " + err.Error() diff --git a/pkg/api/v1alpha1/notification_preferences.go b/pkg/api/v1alpha1/notification_preferences.go new file mode 100644 index 0000000..54259fe --- /dev/null +++ b/pkg/api/v1alpha1/notification_preferences.go @@ -0,0 +1,117 @@ +package v1alpha1 + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/metal-toolbox/governor-api/internal/dbtools" + "github.com/metal-toolbox/governor-api/internal/models" + "github.com/volatiletech/sqlboiler/v4/boil" +) + +// UserNotificationPreferences is an alias export for the same struct in +// dbtools +type UserNotificationPreferences = dbtools.UserNotificationPreferences + +// UserNotificationPreferenceTargets is an alias export for the same struct in +// dbtools +type UserNotificationPreferenceTargets = dbtools.UserNotificationPreferenceTargets + +// handleUpdateNotificationPreferencesRequests handles all notification preferences +// update requests, including those originated from `/users/:id` or `/user` +func handleUpdateNotificationPreferencesRequests( + c *gin.Context, + ex boil.ContextExecutor, + user *models.User, + req UserNotificationPreferences, +) (UserNotificationPreferences, int, error) { + event, err := dbtools.CreateOrUpdateNotificationPreferences( + c.Request.Context(), + user, + req, + ex, + getCtxAuditID(c), + getCtxUser(c), + ) + if err != nil { + return nil, http.StatusBadRequest, err + } + + if err := updateContextWithAuditEventData(c, event); err != nil { + return nil, http.StatusBadRequest, err + } + + np, err := dbtools.GetNotificationPreferences(c.Request.Context(), user.ID, ex, true) + if err != nil { + return nil, http.StatusInternalServerError, err + } + + return np, http.StatusAccepted, nil +} + +// getUserNotificationPreferences returns the authenticated user's notification +// preferences +func (r *Router) getAuthenticatedUserNotificationPreferences(c *gin.Context) { + ctxUser := getCtxUser(c) + if ctxUser == nil { + sendError(c, http.StatusUnauthorized, "no user in context") + return + } + + np, err := dbtools.GetNotificationPreferences(c.Request.Context(), ctxUser.ID, r.DB, true) + if err != nil { + sendError(c, http.StatusInternalServerError, "error getting notification preferences: "+err.Error()) + return + } + + c.JSON(http.StatusOK, np) +} + +// updateUserNotificationPreferences is the http handler for +// /user/notification-preferences +func (r *Router) updateAuthenticatedUserNotificationPreferences(c *gin.Context) { + ctxUser := getCtxUser(c) + if ctxUser == nil { + sendError(c, http.StatusUnauthorized, "no user in context") + return + } + + req := UserNotificationPreferences{} + if err := c.BindJSON(&req); err != nil { + sendError(c, http.StatusBadRequest, "unable to bind request: "+err.Error()) + return + } + + tx, err := r.DB.BeginTx(c.Request.Context(), nil) + if err != nil { + sendError(c, http.StatusBadRequest, "error starting update transaction: "+err.Error()) + return + } + + np, status, err := handleUpdateNotificationPreferencesRequests(c, tx, ctxUser, req) + if err != nil { + msg := err.Error() + + if err := tx.Rollback(); err != nil { + msg += "error rolling back transaction: " + err.Error() + } + + sendError(c, status, msg) + + return + } + + if err := tx.Commit(); err != nil { + msg := "error committing notification preferences update, rolling back: " + err.Error() + + if err := tx.Rollback(); err != nil { + msg += ("error rolling back transaction: " + err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + c.JSON(status, np) +} diff --git a/pkg/api/v1alpha1/notifications_target.go b/pkg/api/v1alpha1/notifications_target.go new file mode 100644 index 0000000..053e98e --- /dev/null +++ b/pkg/api/v1alpha1/notifications_target.go @@ -0,0 +1,472 @@ +package v1alpha1 + +import ( + "database/sql" + "errors" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/gosimple/slug" + "github.com/metal-toolbox/auditevent/ginaudit" + "github.com/metal-toolbox/governor-api/internal/dbtools" + "github.com/metal-toolbox/governor-api/internal/models" + events "github.com/metal-toolbox/governor-api/pkg/events/v1alpha1" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "go.uber.org/zap" +) + +// NotificationTarget is the notification target response +type NotificationTarget struct { + *models.NotificationTarget +} + +// NotificationTargetReq is a request to create a notification target +type NotificationTargetReq struct { + Name string `json:"name"` + Description string `json:"description"` + DefaultEnabled *bool `json:"default_enabled"` +} + +// listNotificationTargets lists notification targets as JSON +func (r *Router) listNotificationTargets(c *gin.Context) { + queryMods := []qm.QueryMod{ + qm.OrderBy("name"), + } + + if _, ok := c.GetQuery("deleted"); ok { + queryMods = append(queryMods, qm.WithDeleted()) + } + + notificationTargets, err := models.NotificationTargets(queryMods...).All(c.Request.Context(), r.DB) + if err != nil { + r.Logger.Error("error fetching notification targets", zap.Error(err)) + sendError(c, http.StatusBadRequest, "error listing notification targets: "+err.Error()) + + return + } + + c.JSON(http.StatusOK, notificationTargets) +} + +// createNotificationTarget creates a notification target in DB +func (r *Router) createNotificationTarget(c *gin.Context) { + req := &NotificationTargetReq{} + if err := c.BindJSON(req); err != nil { + sendError(c, http.StatusBadRequest, "unable to bind request: "+err.Error()) + return + } + + if req.Name == "" { + sendError(c, http.StatusBadRequest, "notification target name is required") + return + } + + if req.Description == "" { + sendError(c, http.StatusBadRequest, "notification target description is required") + return + } + + if req.DefaultEnabled == nil { + sendError(c, http.StatusBadRequest, "notification target default enabled is required") + return + } + + notificationTarget := &models.NotificationTarget{ + Name: req.Name, + Description: req.Description, + DefaultEnabled: *req.DefaultEnabled, + } + + notificationTarget.Slug = slug.Make(notificationTarget.Name) + + tx, err := r.DB.BeginTx(c.Request.Context(), nil) + if err != nil { + sendError(c, http.StatusBadRequest, "error starting notification target create transaction: "+err.Error()) + return + } + + if err := notificationTarget.Insert(c.Request.Context(), tx, boil.Infer()); err != nil { + msg := fmt.Sprintf("error creating notification target: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + event, err := dbtools.AuditNotificationTargetCreated( + c.Request.Context(), + tx, + getCtxAuditID(c), + getCtxUser(c), + notificationTarget, + ) + if err != nil { + msg := fmt.Sprintf("error creating notification target (audit): %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := updateContextWithAuditEventData(c, event); err != nil { + msg := fmt.Sprintf("error creating notification target: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := tx.Commit(); err != nil { + msg := fmt.Sprintf("error committing notification target create: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + // CRDB cannot refresh a materialized view inside explicit transactions + // https://www.cockroachlabs.com/docs/stable/views#known-limitations + if err := dbtools.RefreshNotificationDefaults(c.Request.Context(), r.DB); err != nil { + sendError(c, http.StatusInternalServerError, err.Error()) + return + } + + err = r.EventBus.Publish( + c.Request.Context(), + events.GovernorNotificationTargetsEventSubject, + &events.Event{ + Version: events.Version, + Action: events.GovernorEventCreate, + AuditID: c.GetString(ginaudit.AuditIDContextKey), + ActorID: getCtxActorID(c), + NotificationTargetID: notificationTarget.ID, + }, + ) + if err != nil { + sendError( + c, + http.StatusBadRequest, + fmt.Sprintf( + "failed to publish notification target create event: %s\n%s", + err.Error(), + "downstream changes may be delayed", + ), + ) + + return + } + + c.JSON(http.StatusAccepted, notificationTarget) +} + +// getNotificationTarget fetch a notification target from DB with given id +func (r *Router) getNotificationTarget(c *gin.Context) { + queryMods := []qm.QueryMod{} + id := c.Param("id") + + deleted := false + if _, deleted = c.GetQuery("deleted"); deleted { + queryMods = append(queryMods, qm.WithDeleted()) + } + + q := qm.Where("id = ?", id) + + if _, err := uuid.Parse(id); err != nil { + if deleted { + sendError(c, http.StatusBadRequest, "unable to get deleted notification target by slug, use the id") + return + } + + q = qm.Where("slug = ?", id) + } + + queryMods = append(queryMods, q) + + notificationTarget, err := models.NotificationTargets(queryMods...).One(c.Request.Context(), r.DB) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + sendError(c, http.StatusNotFound, "notification target not found: "+err.Error()) + return + } + + sendError(c, http.StatusInternalServerError, "error getting notification target"+err.Error()) + + return + } + + c.JSON(http.StatusOK, NotificationTarget{notificationTarget}) +} + +// deleteNotificationTarget marks a notification target deleted +func (r *Router) deleteNotificationTarget(c *gin.Context) { + id := c.Param("id") + + q := qm.Where("id = ?", id) + if _, err := uuid.Parse(id); err != nil { + q = qm.Where("slug = ?", id) + } + + n, err := models.NotificationTargets(q).One(c.Request.Context(), r.DB) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + sendError(c, http.StatusNotFound, "notification target not found"+err.Error()) + return + } + + sendError(c, http.StatusInternalServerError, "error getting notification target: "+err.Error()) + + return + } + + tx, err := r.DB.BeginTx(c.Request.Context(), nil) + if err != nil { + sendError(c, http.StatusBadRequest, "error starting delete transaction: "+err.Error()) + return + } + + if _, err := n.Delete(c.Request.Context(), tx, false); err != nil { + msg := fmt.Sprintf("error deleting notification target: %s. rolling back\n", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + event, err := dbtools.AuditNotificationTargetDeleted( + c.Request.Context(), + tx, + getCtxAuditID(c), + getCtxUser(c), + n, + ) + if err != nil { + msg := fmt.Sprintf("error deleting notification target (audit): %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := updateContextWithAuditEventData(c, event); err != nil { + msg := fmt.Sprintf("error deleting notification target: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := tx.Commit(); err != nil { + msg := fmt.Sprintf("error committing notification target delete: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := dbtools.RefreshNotificationDefaults(c.Request.Context(), r.DB); err != nil { + sendError(c, http.StatusInternalServerError, err.Error()) + return + } + + err = r.EventBus.Publish( + c.Request.Context(), + events.GovernorNotificationTargetsEventSubject, + &events.Event{ + Version: events.Version, + Action: events.GovernorEventDelete, + AuditID: c.GetString(ginaudit.AuditIDContextKey), + ActorID: getCtxActorID(c), + NotificationTargetID: n.ID, + }, + ) + if err != nil { + sendError( + c, + http.StatusBadRequest, + fmt.Sprintf( + "failed to publish notification target delete event: %s\n%s", + err.Error(), + "downstream changes may be delayed", + ), + ) + + return + } + + c.JSON(http.StatusAccepted, n) +} + +// updateNotificationTarget updates a notification target in DB +func (r *Router) updateNotificationTarget(c *gin.Context) { + id := c.Param("id") + + q := qm.Where("id = ?", id) + if _, err := uuid.Parse(id); err != nil { + q = qm.Where("slug = ?", id) + } + + n, err := models.NotificationTargets(q).One(c.Request.Context(), r.DB) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + sendError(c, http.StatusNotFound, "notification target not found"+err.Error()) + return + } + + sendError(c, http.StatusInternalServerError, "error getting notification target: "+err.Error()) + + return + } + + original := *n + + req := &NotificationTargetReq{} + if err := c.BindJSON(req); err != nil { + sendError(c, http.StatusBadRequest, "unable to bind request: "+err.Error()) + return + } + + if req.Name != "" { + sendError(c, http.StatusBadRequest, "modifying notification target name is not allowed") + return + } + + if req.Description != "" { + n.Description = req.Description + } + + if req.DefaultEnabled == nil { + sendError(c, http.StatusBadRequest, "notification target default enabled is required") + return + } + + n.DefaultEnabled = *req.DefaultEnabled + + tx, err := r.DB.BeginTx(c.Request.Context(), nil) + if err != nil { + sendError(c, http.StatusBadRequest, "error starting update transaction: "+err.Error()) + return + } + + if _, err := n.Update(c.Request.Context(), tx, boil.Infer()); err != nil { + msg := fmt.Sprintf("error updating notification target: %s. rolling back\n", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + event, err := dbtools.AuditNotificationTargetUpdated( + c.Request.Context(), + tx, + getCtxAuditID(c), + getCtxUser(c), + &original, + n, + ) + if err != nil { + msg := fmt.Sprintf("error updating notification target (audit): %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := updateContextWithAuditEventData(c, event); err != nil { + msg := fmt.Sprintf("error updating notification target: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := tx.Commit(); err != nil { + msg := fmt.Sprintf("error committing notification target update: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := dbtools.RefreshNotificationDefaults(c.Request.Context(), r.DB); err != nil { + sendError(c, http.StatusInternalServerError, err.Error()) + return + } + + err = r.EventBus.Publish( + c.Request.Context(), + events.GovernorNotificationTargetsEventSubject, + &events.Event{ + Version: events.Version, + Action: events.GovernorEventUpdate, + AuditID: c.GetString(ginaudit.AuditIDContextKey), + ActorID: getCtxActorID(c), + NotificationTargetID: n.ID, + }, + ) + if err != nil { + sendError( + c, + http.StatusBadRequest, + fmt.Sprintf( + "failed to publish notification target update event: %s\n%s", + err.Error(), + "downstream changes may be delayed", + ), + ) + + return + } + + c.JSON(http.StatusAccepted, n) +} diff --git a/pkg/api/v1alpha1/notifications_types.go b/pkg/api/v1alpha1/notifications_types.go new file mode 100644 index 0000000..d21a974 --- /dev/null +++ b/pkg/api/v1alpha1/notifications_types.go @@ -0,0 +1,472 @@ +package v1alpha1 + +import ( + "database/sql" + "errors" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/gosimple/slug" + "github.com/metal-toolbox/auditevent/ginaudit" + "github.com/metal-toolbox/governor-api/internal/dbtools" + "github.com/metal-toolbox/governor-api/internal/models" + events "github.com/metal-toolbox/governor-api/pkg/events/v1alpha1" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "go.uber.org/zap" +) + +// NotificationType is the notification type response +type NotificationType struct { + *models.NotificationType +} + +// NotificationTypeReq is a request to create a notification type +type NotificationTypeReq struct { + Name string `json:"name"` + Description string `json:"description"` + DefaultEnabled *bool `json:"default_enabled"` +} + +// listNotificationTypes lists notification types as JSON +func (r *Router) listNotificationTypes(c *gin.Context) { + queryMods := []qm.QueryMod{ + qm.OrderBy("name"), + } + + if _, ok := c.GetQuery("deleted"); ok { + queryMods = append(queryMods, qm.WithDeleted()) + } + + notificationTypes, err := models.NotificationTypes(queryMods...).All(c.Request.Context(), r.DB) + if err != nil { + r.Logger.Error("error fetching notification types", zap.Error(err)) + sendError(c, http.StatusBadRequest, "error listing notification types: "+err.Error()) + + return + } + + c.JSON(http.StatusOK, notificationTypes) +} + +// createNotificationType creates a notification type in DB +func (r *Router) createNotificationType(c *gin.Context) { + req := &NotificationTypeReq{} + if err := c.BindJSON(req); err != nil { + sendError(c, http.StatusBadRequest, "unable to bind request: "+err.Error()) + return + } + + if req.Name == "" { + sendError(c, http.StatusBadRequest, "notification type name is required") + return + } + + if req.Description == "" { + sendError(c, http.StatusBadRequest, "notification type description is required") + return + } + + if req.DefaultEnabled == nil { + sendError(c, http.StatusBadRequest, "notification type default enabled is required") + return + } + + notificationType := &models.NotificationType{ + Name: req.Name, + Description: req.Description, + DefaultEnabled: *req.DefaultEnabled, + } + + notificationType.Slug = slug.Make(notificationType.Name) + + tx, err := r.DB.BeginTx(c.Request.Context(), nil) + if err != nil { + sendError(c, http.StatusBadRequest, "error starting notification type create transaction: "+err.Error()) + return + } + + if err := notificationType.Insert(c.Request.Context(), tx, boil.Infer()); err != nil { + msg := fmt.Sprintf("error creating notification type: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + event, err := dbtools.AuditNotificationTypeCreated( + c.Request.Context(), + tx, + getCtxAuditID(c), + getCtxUser(c), + notificationType, + ) + if err != nil { + msg := fmt.Sprintf("error creating notification type (audit): %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := updateContextWithAuditEventData(c, event); err != nil { + msg := fmt.Sprintf("error creating notification type: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := tx.Commit(); err != nil { + msg := fmt.Sprintf("error committing notification type create: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + // CRDB cannot refresh a materialized view inside explicit transactions + // https://www.cockroachlabs.com/docs/stable/views#known-limitations + if err := dbtools.RefreshNotificationDefaults(c.Request.Context(), r.DB); err != nil { + sendError(c, http.StatusInternalServerError, err.Error()) + return + } + + err = r.EventBus.Publish( + c.Request.Context(), + events.GovernorNotificationTypesEventSubject, + &events.Event{ + Version: events.Version, + Action: events.GovernorEventCreate, + AuditID: c.GetString(ginaudit.AuditIDContextKey), + ActorID: getCtxActorID(c), + NotificationTypeID: notificationType.ID, + }, + ) + if err != nil { + sendError( + c, + http.StatusBadRequest, + fmt.Sprintf( + "failed to publish notification type create event: %s\n%s", + err.Error(), + "downstream changes may be delayed", + ), + ) + + return + } + + c.JSON(http.StatusAccepted, notificationType) +} + +// getNotificationType fetch a notification type from DB with given id +func (r *Router) getNotificationType(c *gin.Context) { + queryMods := []qm.QueryMod{} + id := c.Param("id") + + deleted := false + if _, deleted = c.GetQuery("deleted"); deleted { + queryMods = append(queryMods, qm.WithDeleted()) + } + + q := qm.Where("id = ?", id) + + if _, err := uuid.Parse(id); err != nil { + if deleted { + sendError(c, http.StatusBadRequest, "unable to get deleted notification type by slug, use the id") + return + } + + q = qm.Where("slug = ?", id) + } + + queryMods = append(queryMods, q) + + notificationType, err := models.NotificationTypes(queryMods...).One(c.Request.Context(), r.DB) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + sendError(c, http.StatusNotFound, "notification type not found: "+err.Error()) + return + } + + sendError(c, http.StatusInternalServerError, "error getting notification type"+err.Error()) + + return + } + + c.JSON(http.StatusOK, NotificationType{notificationType}) +} + +// deleteNotificationType marks a notification type deleted +func (r *Router) deleteNotificationType(c *gin.Context) { + id := c.Param("id") + + q := qm.Where("id = ?", id) + if _, err := uuid.Parse(id); err != nil { + q = qm.Where("slug = ?", id) + } + + n, err := models.NotificationTypes(q).One(c.Request.Context(), r.DB) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + sendError(c, http.StatusNotFound, "notification type not found"+err.Error()) + return + } + + sendError(c, http.StatusInternalServerError, "error getting notification type: "+err.Error()) + + return + } + + tx, err := r.DB.BeginTx(c.Request.Context(), nil) + if err != nil { + sendError(c, http.StatusBadRequest, "error starting delete transaction: "+err.Error()) + return + } + + if _, err := n.Delete(c.Request.Context(), tx, false); err != nil { + msg := fmt.Sprintf("error deleting notification type: %s. rolling back\n", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + event, err := dbtools.AuditNotificationTypeDeleted( + c.Request.Context(), + tx, + getCtxAuditID(c), + getCtxUser(c), + n, + ) + if err != nil { + msg := fmt.Sprintf("error deleting notification type (audit): %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := updateContextWithAuditEventData(c, event); err != nil { + msg := fmt.Sprintf("error deleting notification type: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := tx.Commit(); err != nil { + msg := fmt.Sprintf("error committing notification type delete: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := dbtools.RefreshNotificationDefaults(c.Request.Context(), r.DB); err != nil { + sendError(c, http.StatusInternalServerError, err.Error()) + return + } + + err = r.EventBus.Publish( + c.Request.Context(), + events.GovernorNotificationTypesEventSubject, + &events.Event{ + Version: events.Version, + Action: events.GovernorEventDelete, + AuditID: c.GetString(ginaudit.AuditIDContextKey), + ActorID: getCtxActorID(c), + NotificationTypeID: n.ID, + }, + ) + if err != nil { + sendError( + c, + http.StatusBadRequest, + fmt.Sprintf( + "failed to publish notification type delete event: %s\n%s", + err.Error(), + "downstream changes may be delayed", + ), + ) + + return + } + + c.JSON(http.StatusAccepted, n) +} + +// updateNotificationType updates a notification type in DB +func (r *Router) updateNotificationType(c *gin.Context) { + id := c.Param("id") + + q := qm.Where("id = ?", id) + if _, err := uuid.Parse(id); err != nil { + q = qm.Where("slug = ?", id) + } + + n, err := models.NotificationTypes(q).One(c.Request.Context(), r.DB) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + sendError(c, http.StatusNotFound, "notification type not found"+err.Error()) + return + } + + sendError(c, http.StatusInternalServerError, "error getting notification type: "+err.Error()) + + return + } + + original := *n + + req := &NotificationTypeReq{} + if err := c.BindJSON(req); err != nil { + sendError(c, http.StatusBadRequest, "unable to bind request: "+err.Error()) + return + } + + if req.Name != "" { + sendError(c, http.StatusBadRequest, "modifying notification type name is not allowed") + return + } + + if req.Description != "" { + n.Description = req.Description + } + + if req.DefaultEnabled == nil { + sendError(c, http.StatusBadRequest, "notification type default enabled is required") + return + } + + n.DefaultEnabled = *req.DefaultEnabled + + tx, err := r.DB.BeginTx(c.Request.Context(), nil) + if err != nil { + sendError(c, http.StatusBadRequest, "error starting update transaction: "+err.Error()) + return + } + + if _, err := n.Update(c.Request.Context(), tx, boil.Infer()); err != nil { + msg := fmt.Sprintf("error updating notification type: %s. rolling back\n", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + event, err := dbtools.AuditNotificationTypeUpdated( + c.Request.Context(), + tx, + getCtxAuditID(c), + getCtxUser(c), + &original, + n, + ) + if err != nil { + msg := fmt.Sprintf("error updating notification type (audit): %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := updateContextWithAuditEventData(c, event); err != nil { + msg := fmt.Sprintf("error updating notification type: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := tx.Commit(); err != nil { + msg := fmt.Sprintf("error committing notification type update: %s", err.Error()) + + if err := tx.Rollback(); err != nil { + msg += fmt.Sprintf("error rolling back transaction: %s", err.Error()) + } + + sendError(c, http.StatusBadRequest, msg) + + return + } + + if err := dbtools.RefreshNotificationDefaults(c.Request.Context(), r.DB); err != nil { + sendError(c, http.StatusInternalServerError, err.Error()) + return + } + + err = r.EventBus.Publish( + c.Request.Context(), + events.GovernorNotificationTypesEventSubject, + &events.Event{ + Version: events.Version, + Action: events.GovernorEventUpdate, + AuditID: c.GetString(ginaudit.AuditIDContextKey), + ActorID: getCtxActorID(c), + NotificationTypeID: n.ID, + }, + ) + if err != nil { + sendError( + c, + http.StatusBadRequest, + fmt.Sprintf( + "failed to publish notification type update event: %s\n%s", + err.Error(), + "downstream changes may be delayed", + ), + ) + + return + } + + c.JSON(http.StatusAccepted, n) +} diff --git a/pkg/api/v1alpha1/router.go b/pkg/api/v1alpha1/router.go index 70755c6..fe64853 100644 --- a/pkg/api/v1alpha1/router.go +++ b/pkg/api/v1alpha1/router.go @@ -81,6 +81,22 @@ func (r *Router) Routes(rg *gin.RouterGroup) { r.getAuthenticatedUserGroupApprovals, ) + rg.GET( + "/user/notification-preferences", + r.AuditMW.AuditWithType("GetUserNotificationPreferences"), + r.AuthMW.AuthRequired([]string{oidcScope}), + r.mwUserAuthRequired(AuthRoleUser), + r.getAuthenticatedUserNotificationPreferences, + ) + + rg.PUT( + "/user/notification-preferences", + r.AuditMW.AuditWithType("UpdateUserNotificationPreferences"), + r.AuthMW.AuthRequired([]string{oidcScope}), + r.mwUserAuthRequired(AuthRoleUser), + r.updateAuthenticatedUserNotificationPreferences, + ) + rg.GET( "/users", r.AuditMW.AuditWithType("ListUsers"), @@ -476,6 +492,80 @@ func (r *Router) Routes(rg *gin.RouterGroup) { r.AuthMW.AuthRequired(readScopesWithOpenID("governor:applications")), r.listApplicationTypeApps, ) + + rg.GET( + "/notification-types", + r.AuditMW.AuditWithType("ListNotificationTypes"), + r.AuthMW.AuthRequired(readScopesWithOpenID("governor:notifications")), + r.listNotificationTypes, + ) + + rg.GET( + "/notification-types/:id", + r.AuditMW.AuditWithType("GetNotificationType"), + r.AuthMW.AuthRequired(readScopesWithOpenID("governor:notifications")), + r.getNotificationType, + ) + + rg.POST( + "/notification-types", + r.AuditMW.AuditWithType("CreateNotificationType"), + r.AuthMW.AuthRequired(createScopesWithOpenID("governor:notifications")), + r.mwUserAuthRequired(AuthRoleAdmin), + r.createNotificationType, + ) + + rg.PUT( + "/notification-types/:id", + r.AuditMW.AuditWithType("UpdateNotificationType"), + r.AuthMW.AuthRequired(updateScopesWithOpenID("governor:notifications")), + r.updateNotificationType, + ) + + rg.DELETE( + "/notification-types/:id", + r.AuditMW.AuditWithType("DeleteNotificationType"), + r.AuthMW.AuthRequired(deleteScopesWithOpenID("governor:notifications")), + r.mwUserAuthRequired(AuthRoleAdmin), + r.deleteNotificationType, + ) + + rg.GET( + "/notification-targets", + r.AuditMW.AuditWithType("ListNotificationTargets"), + r.AuthMW.AuthRequired(readScopesWithOpenID("governor:notifications")), + r.listNotificationTargets, + ) + + rg.GET( + "/notification-targets/:id", + r.AuditMW.AuditWithType("GetNotificationTarget"), + r.AuthMW.AuthRequired(readScopesWithOpenID("governor:notifications")), + r.getNotificationTarget, + ) + + rg.POST( + "/notification-targets", + r.AuditMW.AuditWithType("CreateNotificationTarget"), + r.AuthMW.AuthRequired(createScopesWithOpenID("governor:notifications")), + r.mwUserAuthRequired(AuthRoleAdmin), + r.createNotificationTarget, + ) + + rg.PUT( + "/notification-targets/:id", + r.AuditMW.AuditWithType("UpdateNotificationTarget"), + r.AuthMW.AuthRequired(updateScopesWithOpenID("governor:notifications")), + r.updateNotificationTarget, + ) + + rg.DELETE( + "/notification-targets/:id", + r.AuditMW.AuditWithType("DeleteNotificationTarget"), + r.AuthMW.AuthRequired(deleteScopesWithOpenID("governor:notifications")), + r.mwUserAuthRequired(AuthRoleAdmin), + r.deleteNotificationTarget, + ) } func contains(list []string, item string) bool { diff --git a/pkg/api/v1alpha1/users.go b/pkg/api/v1alpha1/users.go index 82f57e7..8ca6b60 100644 --- a/pkg/api/v1alpha1/users.go +++ b/pkg/api/v1alpha1/users.go @@ -34,9 +34,10 @@ var permittedListUsersParams = []string{"external_id", "email"} // User is a user response type User struct { *models.User - Memberships []string `json:"memberships,omitempty"` - MembershipsDirect []string `json:"memberships_direct,omitempty"` - MembershipRequests []string `json:"membership_requests,omitempty"` + Memberships []string `json:"memberships,omitempty"` + MembershipsDirect []string `json:"memberships_direct,omitempty"` + MembershipRequests []string `json:"membership_requests,omitempty"` + NotificationPreferences dbtools.UserNotificationPreferences `json:"notification_preferences,omitempty"` } // UserReq is a user request payload @@ -152,11 +153,18 @@ func (r *Router) getUser(c *gin.Context) { requests[i] = r.GroupID } + notificationPreferences, err := dbtools.GetNotificationPreferences(c.Request.Context(), id, r.DB, true) + if err != nil { + sendError(c, http.StatusInternalServerError, "error getting notification preferences: "+err.Error()) + return + } + c.JSON(http.StatusOK, User{ - User: user, - Memberships: memberships, - MembershipsDirect: membershipsDirect, - MembershipRequests: requests, + User: user, + Memberships: memberships, + MembershipsDirect: membershipsDirect, + MembershipRequests: requests, + NotificationPreferences: notificationPreferences, }) } diff --git a/pkg/client/errors.go b/pkg/client/errors.go index 798e014..7e92bac 100644 --- a/pkg/client/errors.go +++ b/pkg/client/errors.go @@ -35,4 +35,16 @@ var ( // ErrUserNotFound is returned when a user is expected to be returned but instead is not ErrUserNotFound = errors.New("user not found") + + // ErrNotificationTypeNotFound is returned when a notification type is not found + ErrNotificationTypeNotFound = errors.New("notification type not found") + + // ErrMissingNotificationTypeID is returned when a a missing or bad notification type ID is passed to a request + ErrMissingNotificationTypeID = errors.New("missing notification type id in request") + + // ErrNotificationTargetNotFound is returned when a notification target is not found + ErrNotificationTargetNotFound = errors.New("notification target not found") + + // ErrMissingNotificationTargetID is returned when a a missing or bad notification target ID is passed to a request + ErrMissingNotificationTargetID = errors.New("missing notification target id in request") ) diff --git a/pkg/client/notification_preferences.go b/pkg/client/notification_preferences.go new file mode 100644 index 0000000..9bbf7dc --- /dev/null +++ b/pkg/client/notification_preferences.go @@ -0,0 +1,51 @@ +package client + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/metal-toolbox/governor-api/pkg/api/v1alpha1" +) + +// NotificationPreferences list all notification preferences for a user +func (c *Client) NotificationPreferences(ctx context.Context, userID string) (v1alpha1.UserNotificationPreferences, error) { + u := fmt.Sprintf( + "%s/api/%s/users/%s/notification-preferences", + c.url, + governorAPIVersionAlpha, + userID, + ) + + req, err := c.newGovernorRequest(ctx, http.MethodGet, u) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return nil, ErrRequestNonSuccess + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + p := v1alpha1.UserNotificationPreferences{} + if err := json.Unmarshal(respBody, &p); err != nil { + return nil, err + } + + return p, nil +} diff --git a/pkg/client/notification_preferences_test.go b/pkg/client/notification_preferences_test.go new file mode 100644 index 0000000..c804357 --- /dev/null +++ b/pkg/client/notification_preferences_test.go @@ -0,0 +1,149 @@ +package client + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/metal-toolbox/governor-api/pkg/api/v1alpha1" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "golang.org/x/oauth2" +) + +const ( + testNotificationPreferencesResponse = `[ + { + "notification_type": "defcon-1", + "enabled": true, + "notification_targets": [ + { + "target": "slack", + "enabled": true + }, + { + "target": "email", + "enabled": true + } + ] + }, + { + "notification_type": "notice", + "enabled": false, + "notification_targets": [ + { + "target": "slack", + "enabled": true + }, + { + "target": "email", + "enabled": true + } + ] + }, + { + "notification_type": "alert", + "enabled": true, + "notification_targets": [ + { + "target": "slack", + "enabled": true + }, + { + "target": "email", + "enabled": true + } + ] + } + ]` +) + +func TestClient_NotificationPreferences(t *testing.T) { + testResp := func(r []byte) v1alpha1.UserNotificationPreferences { + resp := v1alpha1.UserNotificationPreferences{} + if err := json.Unmarshal(r, &resp); err != nil { + t.Error(err) + } + + return resp + } + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + userID string + fields fields + want v1alpha1.UserNotificationPreferences + wantErr bool + }{ + { + name: "example request", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationPreferencesResponse), + statusCode: http.StatusOK, + }, + }, + userID: "186c5a52-4421-4573-8bbf-78d85d3c277e", + want: testResp([]byte(testNotificationPreferencesResponse)), + }, + { + name: "non-success", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + userID: "186c5a52-4421-4573-8bbf-78d85d3c277e", + wantErr: true, + }, + { + name: "bad json response", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`{`), + }, + }, + userID: "186c5a52-4421-4573-8bbf-78d85d3c277e", + wantErr: true, + }, + { + name: "missing user ID", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + got, err := c.NotificationPreferences(context.TODO(), "") + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/client/notification_targets.go b/pkg/client/notification_targets.go new file mode 100644 index 0000000..fadfced --- /dev/null +++ b/pkg/client/notification_targets.go @@ -0,0 +1,239 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/metal-toolbox/governor-api/pkg/api/v1alpha1" +) + +// NotificationTarget fetch a notification target +func (c *Client) NotificationTarget(ctx context.Context, idOrSlug string, deleted bool) (*v1alpha1.NotificationTarget, error) { + if idOrSlug == "" { + return nil, ErrMissingNotificationTargetID + } + + u := fmt.Sprintf( + "%s/api/%s/notification-targets/%s", + c.url, + governorAPIVersionAlpha, + idOrSlug, + ) + if deleted { + u += "?deleted" + } + + req, err := c.newGovernorRequest(ctx, http.MethodGet, u) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return nil, ErrGroupNotFound + } + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return nil, ErrRequestNonSuccess + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + nt := &v1alpha1.NotificationTarget{} + if err := json.Unmarshal(respBody, nt); err != nil { + return nil, err + } + + return nt, nil +} + +// NotificationTargets list all notification targets +func (c *Client) NotificationTargets(ctx context.Context, deleted bool) ([]*v1alpha1.NotificationTarget, error) { + u := fmt.Sprintf( + "%s/api/%s/notification-targets", + c.url, + governorAPIVersionAlpha, + ) + if deleted { + u += "?deleted" + } + + req, err := c.newGovernorRequest(ctx, http.MethodGet, u) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return nil, ErrRequestNonSuccess + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + nt := []*v1alpha1.NotificationTarget{} + if err := json.Unmarshal(respBody, &nt); err != nil { + return nil, err + } + + return nt, nil +} + +// CreateNotificationTarget creates a notification target +func (c *Client) CreateNotificationTarget( + ctx context.Context, ntReq *v1alpha1.NotificationTargetReq, +) (*v1alpha1.NotificationTarget, error) { + req, err := c.newGovernorRequest( + ctx, http.MethodPost, + fmt.Sprintf("%s/api/%s/notification-targets", c.url, governorAPIVersionAlpha), + ) + if err != nil { + return nil, err + } + + ntReqJSON, err := json.Marshal(ntReq) + if err != nil { + return nil, err + } + + req.Body = io.NopCloser(bytes.NewReader(ntReqJSON)) + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return nil, ErrRequestNonSuccess + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + nt := &v1alpha1.NotificationTarget{} + if err := json.Unmarshal(respBody, nt); err != nil { + return nil, err + } + + return nt, nil +} + +// UpdateNotificationTarget updates a notification target +func (c *Client) UpdateNotificationTarget( + ctx context.Context, idOrSlug string, ntReq *v1alpha1.NotificationTargetReq, +) (*v1alpha1.NotificationTarget, error) { + if idOrSlug == "" { + return nil, ErrMissingNotificationTargetID + } + + req, err := c.newGovernorRequest( + ctx, http.MethodPut, + fmt.Sprintf( + "%s/api/%s/notification-targets/%s", + c.url, + governorAPIVersionAlpha, + idOrSlug, + ), + ) + if err != nil { + return nil, err + } + + ntReqJSON, err := json.Marshal(ntReq) + if err != nil { + return nil, err + } + + req.Body = io.NopCloser(bytes.NewReader(ntReqJSON)) + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return nil, ErrRequestNonSuccess + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + nt := &v1alpha1.NotificationTarget{} + if err := json.Unmarshal(respBody, nt); err != nil { + return nil, err + } + + return nt, nil +} + +// DeleteNotificationTarget deletes a notification target +func (c *Client) DeleteNotificationTarget(ctx context.Context, idOrSlug string) error { + if idOrSlug == "" { + return ErrMissingNotificationTargetID + } + + req, err := c.newGovernorRequest( + ctx, http.MethodDelete, + fmt.Sprintf( + "%s/api/%s/notification-targets/%s", + c.url, + governorAPIVersionAlpha, + idOrSlug, + ), + ) + if err != nil { + return err + } + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return ErrRequestNonSuccess + } + + return nil +} diff --git a/pkg/client/notification_targets_test.go b/pkg/client/notification_targets_test.go new file mode 100644 index 0000000..875496b --- /dev/null +++ b/pkg/client/notification_targets_test.go @@ -0,0 +1,544 @@ +package client + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/metal-toolbox/governor-api/pkg/api/v1alpha1" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "golang.org/x/oauth2" +) + +const ( + testNotificationTargetsResponse = `[ + { + "id": "16e44109-f49b-4ce7-911b-0a54bcc9a351", + "name": "Email", + "slug": "email", + "description": "less nice", + "default_enabled": true, + "created_at": "2023-07-27T21:36:11.029099Z", + "updated_at": "2023-07-27T21:36:11.029099Z", + "deleted_at": null + }, + { + "id": "03e02f7f-3d36-4423-9165-cbe3deabbf04", + "name": "MS Teams", + "slug": "ms-teams", + "description": "less less nice", + "default_enabled": true, + "created_at": "2023-07-27T21:36:25.455371Z", + "updated_at": "2023-07-27T21:36:25.455371Z", + "deleted_at": null + }, + { + "id": "71e05156-ee15-4f99-ba34-94f38ecc5438", + "name": "Slack", + "slug": "slack", + "description": "nice", + "default_enabled": true, + "created_at": "2023-07-27T21:36:07.108261Z", + "updated_at": "2023-07-27T21:36:07.108261Z", + "deleted_at": null + } + ]` + + testNotificationTargetResponse = `{ + "id": "71e05156-ee15-4f99-ba34-94f38ecc5438", + "name": "Slack", + "slug": "slack", + "description": "nice", + "default_enabled": true, + "created_at": "2023-07-27T21:36:07.108261Z", + "updated_at": "2023-07-27T21:36:07.108261Z", + "deleted_at": null + }` +) + +func TestClient_NotificationTargets(t *testing.T) { + testResp := func(r []byte) []*v1alpha1.NotificationTarget { + resp := []*v1alpha1.NotificationTarget{} + if err := json.Unmarshal(r, &resp); err != nil { + t.Error(err) + } + + return resp + } + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + fields fields + want []*v1alpha1.NotificationTarget + wantErr bool + }{ + { + name: "example request", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTargetsResponse), + statusCode: http.StatusOK, + }, + }, + want: testResp([]byte(testNotificationTargetsResponse)), + }, + { + name: "non-success", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + wantErr: true, + }, + { + name: "bad json response", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`{`), + }, + }, + wantErr: true, + }, + { + name: "null response", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`null`), + }, + }, + want: []*v1alpha1.NotificationTarget(nil), + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + got, err := c.NotificationTargets(context.TODO(), false) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_NotificationTarget(t *testing.T) { + testResp := func(r []byte) *v1alpha1.NotificationTarget { + resp := &v1alpha1.NotificationTarget{} + if err := json.Unmarshal(r, resp); err != nil { + t.Error(err) + } + + return resp + } + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + fields fields + want *v1alpha1.NotificationTarget + wantErr bool + id string + }{ + { + name: "example request", + id: "71e05156-ee15-4f99-ba34-94f38ecc5438", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTargetResponse), + statusCode: http.StatusOK, + }, + }, + want: testResp([]byte(testNotificationTargetResponse)), + }, + { + name: "non-success", + id: "71e05156-ee15-4f99-ba34-94f38ecc5438", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + wantErr: true, + }, + { + name: "bad json response", + id: "71e05156-ee15-4f99-ba34-94f38ecc5438", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`{`), + }, + }, + wantErr: true, + }, + { + name: "missing id", + id: "71e05156-ee15-4f99-ba34-94f38ecc5438", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + got, err := c.NotificationTarget(context.TODO(), tt.id, false) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_CreateNotificationTarget(t *testing.T) { + testResp := func(r []byte) *v1alpha1.NotificationTarget { + resp := &v1alpha1.NotificationTarget{} + if err := json.Unmarshal(r, resp); err != nil { + t.Error(err) + } + + return resp + } + + enabled := true + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + fields fields + req *v1alpha1.NotificationTargetReq + want *v1alpha1.NotificationTarget + wantErr bool + }{ + { + name: "example request", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTargetResponse), + statusCode: http.StatusOK, + }, + }, + req: &v1alpha1.NotificationTargetReq{ + Name: "Slack", + Description: "nice", + DefaultEnabled: &enabled, + }, + want: testResp([]byte(testNotificationTargetResponse)), + }, + { + name: "example request status accepted", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTargetResponse), + statusCode: http.StatusAccepted, + }, + }, + req: &v1alpha1.NotificationTargetReq{ + Name: "Slack", + Description: "nice", + DefaultEnabled: &enabled, + }, + want: testResp([]byte(testNotificationTargetResponse)), + }, + { + name: "non-success", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + req: &v1alpha1.NotificationTargetReq{ + Name: "Slack", + Description: "nice", + DefaultEnabled: &enabled, + }, + wantErr: true, + }, + { + name: "bad json response", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`{`), + }, + }, + req: &v1alpha1.NotificationTargetReq{ + Name: "Slack", + Description: "nice", + DefaultEnabled: &enabled, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + got, err := c.CreateNotificationTarget(context.TODO(), tt.req) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_UpdateNotificationTarget(t *testing.T) { + testResp := func(r []byte) *v1alpha1.NotificationTarget { + resp := &v1alpha1.NotificationTarget{} + if err := json.Unmarshal(r, resp); err != nil { + t.Error(err) + } + + return resp + } + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + fields fields + id string + req *v1alpha1.NotificationTargetReq + want *v1alpha1.NotificationTarget + wantErr bool + }{ + { + name: "example request", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTargetResponse), + statusCode: http.StatusOK, + }, + }, + id: "71e05156-ee15-4f99-ba34-94f38ecc5438", + req: &v1alpha1.NotificationTargetReq{ + Name: "Alert", + Description: "alert", + }, + want: testResp([]byte(testNotificationTargetResponse)), + }, + { + name: "example request status accepted", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTargetResponse), + statusCode: http.StatusAccepted, + }, + }, + id: "71e05156-ee15-4f99-ba34-94f38ecc5438", + req: &v1alpha1.NotificationTargetReq{ + Name: "Alert", + Description: "alert", + }, + want: testResp([]byte(testNotificationTargetResponse)), + }, + { + name: "non-success", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + id: "71e05156-ee15-4f99-ba34-94f38ecc5438", + req: &v1alpha1.NotificationTargetReq{ + Name: "Alert", + Description: "alert", + }, + wantErr: true, + }, + { + name: "bad json response", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`{`), + }, + }, + id: "71e05156-ee15-4f99-ba34-94f38ecc5438", + req: &v1alpha1.NotificationTargetReq{ + Name: "Alert", + Description: "alert", + }, + wantErr: true, + }, + { + name: "missing id", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTargetResponse), + statusCode: http.StatusOK, + }, + }, + req: &v1alpha1.NotificationTargetReq{ + Name: "Alert", + Description: "alert", + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + got, err := c.UpdateNotificationTarget(context.TODO(), tt.id, tt.req) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_DeleteNotificationTarget(t *testing.T) { + testResp := func(r []byte) *v1alpha1.NotificationTarget { + resp := &v1alpha1.NotificationTarget{} + if err := json.Unmarshal(r, resp); err != nil { + t.Error(err) + } + + return resp + } + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + fields fields + want *v1alpha1.NotificationTarget + wantErr bool + id string + }{ + { + name: "example request", + id: "71e05156-ee15-4f99-ba34-94f38ecc5438", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTargetResponse), + statusCode: http.StatusOK, + }, + }, + want: testResp([]byte(testNotificationTargetResponse)), + }, + { + name: "non-success", + id: "71e05156-ee15-4f99-ba34-94f38ecc5438", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + wantErr: true, + }, + { + name: "missing id", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + err := c.DeleteNotificationTarget(context.TODO(), tt.id) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + }) + } +} diff --git a/pkg/client/notification_types.go b/pkg/client/notification_types.go new file mode 100644 index 0000000..c12b402 --- /dev/null +++ b/pkg/client/notification_types.go @@ -0,0 +1,239 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/metal-toolbox/governor-api/pkg/api/v1alpha1" +) + +// NotificationType fetch a notification type +func (c *Client) NotificationType(ctx context.Context, idOrSlug string, deleted bool) (*v1alpha1.NotificationType, error) { + if idOrSlug == "" { + return nil, ErrMissingNotificationTypeID + } + + u := fmt.Sprintf( + "%s/api/%s/notification-types/%s", + c.url, + governorAPIVersionAlpha, + idOrSlug, + ) + if deleted { + u += "?deleted" + } + + req, err := c.newGovernorRequest(ctx, http.MethodGet, u) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return nil, ErrGroupNotFound + } + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return nil, ErrRequestNonSuccess + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + nt := &v1alpha1.NotificationType{} + if err := json.Unmarshal(respBody, nt); err != nil { + return nil, err + } + + return nt, nil +} + +// NotificationTypes list all notification types +func (c *Client) NotificationTypes(ctx context.Context, deleted bool) ([]*v1alpha1.NotificationType, error) { + u := fmt.Sprintf( + "%s/api/%s/notification-types", + c.url, + governorAPIVersionAlpha, + ) + if deleted { + u += "?deleted" + } + + req, err := c.newGovernorRequest(ctx, http.MethodGet, u) + if err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return nil, ErrRequestNonSuccess + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + nt := []*v1alpha1.NotificationType{} + if err := json.Unmarshal(respBody, &nt); err != nil { + return nil, err + } + + return nt, nil +} + +// CreateNotificationType creates a notification type +func (c *Client) CreateNotificationType( + ctx context.Context, ntReq *v1alpha1.NotificationTypeReq, +) (*v1alpha1.NotificationType, error) { + req, err := c.newGovernorRequest( + ctx, http.MethodPost, + fmt.Sprintf("%s/api/%s/notification-types", c.url, governorAPIVersionAlpha), + ) + if err != nil { + return nil, err + } + + ntReqJSON, err := json.Marshal(ntReq) + if err != nil { + return nil, err + } + + req.Body = io.NopCloser(bytes.NewReader(ntReqJSON)) + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return nil, ErrRequestNonSuccess + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + nt := &v1alpha1.NotificationType{} + if err := json.Unmarshal(respBody, nt); err != nil { + return nil, err + } + + return nt, nil +} + +// UpdateNotificationType updates a notification type +func (c *Client) UpdateNotificationType( + ctx context.Context, idOrSlug string, ntReq *v1alpha1.NotificationTypeReq, +) (*v1alpha1.NotificationType, error) { + if idOrSlug == "" { + return nil, ErrMissingNotificationTypeID + } + + req, err := c.newGovernorRequest( + ctx, http.MethodPut, + fmt.Sprintf( + "%s/api/%s/notification-types/%s", + c.url, + governorAPIVersionAlpha, + idOrSlug, + ), + ) + if err != nil { + return nil, err + } + + ntReqJSON, err := json.Marshal(ntReq) + if err != nil { + return nil, err + } + + req.Body = io.NopCloser(bytes.NewReader(ntReqJSON)) + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return nil, ErrRequestNonSuccess + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + nt := &v1alpha1.NotificationType{} + if err := json.Unmarshal(respBody, nt); err != nil { + return nil, err + } + + return nt, nil +} + +// DeleteNotificationType deletes a notification type +func (c *Client) DeleteNotificationType(ctx context.Context, idOrSlug string) error { + if idOrSlug == "" { + return ErrMissingNotificationTypeID + } + + req, err := c.newGovernorRequest( + ctx, http.MethodDelete, + fmt.Sprintf( + "%s/api/%s/notification-types/%s", + c.url, + governorAPIVersionAlpha, + idOrSlug, + ), + ) + if err != nil { + return err + } + + resp, err := c.httpClient.Do(req.WithContext(ctx)) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && + resp.StatusCode != http.StatusAccepted && + resp.StatusCode != http.StatusNoContent { + return ErrRequestNonSuccess + } + + return nil +} diff --git a/pkg/client/notification_types_test.go b/pkg/client/notification_types_test.go new file mode 100644 index 0000000..06bb4f1 --- /dev/null +++ b/pkg/client/notification_types_test.go @@ -0,0 +1,538 @@ +package client + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/metal-toolbox/governor-api/pkg/api/v1alpha1" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "golang.org/x/oauth2" +) + +const ( + testNotificationTypesResponse = `[ + { + "id": "21037d41-53ee-4144-b39b-6b2eb5761a30", + "name": "Alert", + "slug": "alert", + "description": "alert", + "default_enabled": true, + "created_at": "2023-07-26T21:32:51.788685Z", + "updated_at": "2023-07-26T21:32:51.788685Z", + "deleted_at": null + }, + { + "id": "bcf7b896-31ad-4f61-81b2-534421ed3f4e", + "name": "DEFCON-1", + "slug": "defcon-1", + "description": "defcon-1", + "default_enabled": true, + "created_at": "2023-07-26T21:33:05.637421Z", + "updated_at": "2023-07-26T21:33:05.637421Z", + "deleted_at": null + }, + { + "id": "e638b5a4-a471-43c2-9107-6c7a7deb669c", + "name": "Notice", + "slug": "notice", + "description": "notice", + "default_enabled": false, + "created_at": "2023-07-26T21:32:40.560876Z", + "updated_at": "2023-07-26T21:33:27.78482Z", + "deleted_at": null + } + ]` + + testNotificationTypeResponse = `{ + "id": "21037d41-53ee-4144-b39b-6b2eb5761a30", + "name": "Alert", + "slug": "alert", + "description": "alert", + "default_enabled": true, + "created_at": "2023-07-26T21:32:51.788685Z", + "updated_at": "2023-07-26T21:32:51.788685Z", + "deleted_at": null + }` +) + +func TestClient_NotificationTypes(t *testing.T) { + testResp := func(r []byte) []*v1alpha1.NotificationType { + resp := []*v1alpha1.NotificationType{} + if err := json.Unmarshal(r, &resp); err != nil { + t.Error(err) + } + + return resp + } + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + fields fields + want []*v1alpha1.NotificationType + wantErr bool + }{ + { + name: "example request", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTypesResponse), + statusCode: http.StatusOK, + }, + }, + want: testResp([]byte(testNotificationTypesResponse)), + }, + { + name: "non-success", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + wantErr: true, + }, + { + name: "bad json response", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`{`), + }, + }, + wantErr: true, + }, + { + name: "null response", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`null`), + }, + }, + want: []*v1alpha1.NotificationType(nil), + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + got, err := c.NotificationTypes(context.TODO(), false) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_NotificationType(t *testing.T) { + testResp := func(r []byte) *v1alpha1.NotificationType { + resp := &v1alpha1.NotificationType{} + if err := json.Unmarshal(r, resp); err != nil { + t.Error(err) + } + + return resp + } + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + fields fields + want *v1alpha1.NotificationType + wantErr bool + id string + }{ + { + name: "example request", + id: "21037d41-53ee-4144-b39b-6b2eb5761a30", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTypeResponse), + statusCode: http.StatusOK, + }, + }, + want: testResp([]byte(testNotificationTypeResponse)), + }, + { + name: "non-success", + id: "21037d41-53ee-4144-b39b-6b2eb5761a30", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + wantErr: true, + }, + { + name: "bad json response", + id: "21037d41-53ee-4144-b39b-6b2eb5761a30", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`{`), + }, + }, + wantErr: true, + }, + { + name: "missing id", + id: "21037d41-53ee-4144-b39b-6b2eb5761a30", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + got, err := c.NotificationType(context.TODO(), tt.id, false) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_CreateNotificationType(t *testing.T) { + testResp := func(r []byte) *v1alpha1.NotificationType { + resp := &v1alpha1.NotificationType{} + if err := json.Unmarshal(r, resp); err != nil { + t.Error(err) + } + + return resp + } + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + fields fields + req *v1alpha1.NotificationTypeReq + want *v1alpha1.NotificationType + wantErr bool + }{ + { + name: "example request", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTypeResponse), + statusCode: http.StatusOK, + }, + }, + req: &v1alpha1.NotificationTypeReq{ + Name: "Alert", + Description: "alert", + }, + want: testResp([]byte(testNotificationTypeResponse)), + }, + { + name: "example request status accepted", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTypeResponse), + statusCode: http.StatusAccepted, + }, + }, + req: &v1alpha1.NotificationTypeReq{ + Name: "Alert", + Description: "alert", + }, + want: testResp([]byte(testNotificationTypeResponse)), + }, + { + name: "non-success", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + req: &v1alpha1.NotificationTypeReq{ + Name: "Alert", + Description: "alert", + }, + wantErr: true, + }, + { + name: "bad json response", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`{`), + }, + }, + req: &v1alpha1.NotificationTypeReq{ + Name: "Alert", + Description: "alert", + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + got, err := c.CreateNotificationType(context.TODO(), tt.req) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_UpdateNotificationType(t *testing.T) { + testResp := func(r []byte) *v1alpha1.NotificationType { + resp := &v1alpha1.NotificationType{} + if err := json.Unmarshal(r, resp); err != nil { + t.Error(err) + } + + return resp + } + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + fields fields + id string + req *v1alpha1.NotificationTypeReq + want *v1alpha1.NotificationType + wantErr bool + }{ + { + name: "example request", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTypeResponse), + statusCode: http.StatusOK, + }, + }, + id: "21037d41-53ee-4144-b39b-6b2eb5761a30", + req: &v1alpha1.NotificationTypeReq{ + Name: "Alert", + Description: "alert", + }, + want: testResp([]byte(testNotificationTypeResponse)), + }, + { + name: "example request status accepted", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTypeResponse), + statusCode: http.StatusAccepted, + }, + }, + id: "21037d41-53ee-4144-b39b-6b2eb5761a30", + req: &v1alpha1.NotificationTypeReq{ + Name: "Alert", + Description: "alert", + }, + want: testResp([]byte(testNotificationTypeResponse)), + }, + { + name: "non-success", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + id: "21037d41-53ee-4144-b39b-6b2eb5761a30", + req: &v1alpha1.NotificationTypeReq{ + Name: "Alert", + Description: "alert", + }, + wantErr: true, + }, + { + name: "bad json response", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + resp: []byte(`{`), + }, + }, + id: "21037d41-53ee-4144-b39b-6b2eb5761a30", + req: &v1alpha1.NotificationTypeReq{ + Name: "Alert", + Description: "alert", + }, + wantErr: true, + }, + { + name: "missing id", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTypeResponse), + statusCode: http.StatusOK, + }, + }, + req: &v1alpha1.NotificationTypeReq{ + Name: "Alert", + Description: "alert", + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + got, err := c.UpdateNotificationType(context.TODO(), tt.id, tt.req) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestClient_DeleteNotificationType(t *testing.T) { + testResp := func(r []byte) *v1alpha1.NotificationType { + resp := &v1alpha1.NotificationType{} + if err := json.Unmarshal(r, resp); err != nil { + t.Error(err) + } + + return resp + } + + type fields struct { + httpClient HTTPDoer + } + + tests := []struct { + name string + fields fields + want *v1alpha1.NotificationType + wantErr bool + id string + }{ + { + name: "example request", + id: "21037d41-53ee-4144-b39b-6b2eb5761a30", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + resp: []byte(testNotificationTypeResponse), + statusCode: http.StatusOK, + }, + }, + want: testResp([]byte(testNotificationTypeResponse)), + }, + { + name: "non-success", + id: "21037d41-53ee-4144-b39b-6b2eb5761a30", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusInternalServerError, + }, + }, + wantErr: true, + }, + { + name: "missing id", + fields: fields{ + httpClient: &mockHTTPDoer{ + t: t, + statusCode: http.StatusOK, + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Client{ + url: "https://the.gov/", + logger: zap.NewNop(), + httpClient: tt.fields.httpClient, + clientCredentialConfig: &mockTokener{t: t}, + token: &oauth2.Token{AccessToken: "topSekret"}, + } + err := c.DeleteNotificationType(context.TODO(), tt.id) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + }) + } +} diff --git a/pkg/events/v1alpha1/events.go b/pkg/events/v1alpha1/events.go index b560756..fb0be5e 100644 --- a/pkg/events/v1alpha1/events.go +++ b/pkg/events/v1alpha1/events.go @@ -35,16 +35,22 @@ const ( GovernorApplicationLinkRequestsEventSubject = "applinks.requests" // GovernorApplicationTypesEventSubject is the subject name for application type events (minus the subject prefix) GovernorApplicationTypesEventSubject = "applicationtypes" + // GovernorNotificationTypesEventSubject is the subject name for notification type events (minus the subject prefix) + GovernorNotificationTypesEventSubject = "notification.types" + // GovernorNotificationTargetsEventSubject is the subject name for notification target events (minus the subject prefix) + GovernorNotificationTargetsEventSubject = "notification.targets" ) // Event is an event notification from Governor. type Event struct { - Version string `json:"version"` - Action string `json:"action"` - AuditID string `json:"audit_id,omitempty"` - GroupID string `json:"group_id,omitempty"` - UserID string `json:"user_id,omitempty"` - ActorID string `json:"actor_id,omitempty"` - ApplicationID string `json:"application_id,omitempty"` - ApplicationTypeID string `json:"application_type_id,omitempty"` + Version string `json:"version"` + Action string `json:"action"` + AuditID string `json:"audit_id,omitempty"` + GroupID string `json:"group_id,omitempty"` + UserID string `json:"user_id,omitempty"` + ActorID string `json:"actor_id,omitempty"` + ApplicationID string `json:"application_id,omitempty"` + ApplicationTypeID string `json:"application_type_id,omitempty"` + NotificationTypeID string `json:"notification_type_id,omitempty"` + NotificationTargetID string `json:"notification_target_id,omitempty"` } diff --git a/sqlboiler.toml b/sqlboiler.toml index 310f320..116c032 100644 --- a/sqlboiler.toml +++ b/sqlboiler.toml @@ -9,4 +9,4 @@ host = "localhost" port = 26257 user = "root" sslmode = "disable" -blacklist = ["goose_db_version"] +blacklist = ["goose_db_version", "notification_defaults"]