Sfoglia il codice sorgente

lapi: fix ipv6 operations (#567)

AlteredCoder 4 anni fa
parent
commit
5544000d38
83 ha cambiato i file con 3706 aggiunte e 2445 eliminazioni
  1. 1 1
      .github/workflows/ci_functests-install.yml
  2. 31 0
      .github/workflows/ci_functests-ip_mgmt.yml
  3. 7 0
      cmd/crowdsec-cli/alerts.go
  4. 8 19
      cmd/crowdsec-cli/decisions.go
  5. 4 4
      go.mod
  6. 17 13
      go.sum
  7. 2 0
      pkg/apiclient/alerts_service.go
  8. 0 6
      pkg/apiclient/alerts_service_test.go
  9. 3 0
      pkg/apiclient/decisions_service.go
  10. 2 6
      pkg/apiclient/decisions_service_test.go
  11. 15 15
      pkg/apiserver/alerts_test.go
  12. 14 13
      pkg/apiserver/apic.go
  13. 0 65
      pkg/apiserver/controllers/utils.go
  14. 1 11
      pkg/apiserver/controllers/v1/alerts.go
  15. 0 2
      pkg/apiserver/controllers/v1/decisions.go
  16. 14 14
      pkg/apiserver/decisions_test.go
  17. 0 2
      pkg/apiserver/tests/alertWithInvalidMachineID_sample.json
  18. 0 2
      pkg/apiserver/tests/alert_sample.json
  19. 0 2
      pkg/apiserver/tests/invalidAlert_sample.json
  20. 0 54
      pkg/apiserver/utils_test.go
  21. 0 33
      pkg/csprofiles/csprofiles.go
  22. 91 25
      pkg/database/alerts.go
  23. 240 83
      pkg/database/decisions.go
  24. 176 164
      pkg/database/ent/alert.go
  25. 8 8
      pkg/database/ent/alert/alert.go
  26. 3 3
      pkg/database/ent/alert/where.go
  27. 54 54
      pkg/database/ent/alert_create.go
  28. 5 6
      pkg/database/ent/alert_delete.go
  29. 98 79
      pkg/database/ent/alert_query.go
  30. 132 133
      pkg/database/ent/alert_update.go
  31. 90 73
      pkg/database/ent/bouncer.go
  32. 5 5
      pkg/database/ent/bouncer/bouncer.go
  33. 3 3
      pkg/database/ent/bouncer/where.go
  34. 19 19
      pkg/database/ent/bouncer_create.go
  35. 5 6
      pkg/database/ent/bouncer_delete.go
  36. 74 59
      pkg/database/ent/bouncer_query.go
  37. 50 51
      pkg/database/ent/bouncer_update.go
  38. 7 7
      pkg/database/ent/client.go
  39. 3 3
      pkg/database/ent/context.go
  40. 135 95
      pkg/database/ent/decision.go
  41. 12 3
      pkg/database/ent/decision/decision.go
  42. 294 3
      pkg/database/ent/decision/where.go
  43. 87 21
      pkg/database/ent/decision_create.go
  44. 5 6
      pkg/database/ent/decision_delete.go
  45. 78 65
      pkg/database/ent/decision_query.go
  46. 338 57
      pkg/database/ent/decision_update.go
  47. 3 3
      pkg/database/ent/ent.go
  48. 61 53
      pkg/database/ent/event.go
  49. 2 2
      pkg/database/ent/event/event.go
  50. 3 3
      pkg/database/ent/event/where.go
  51. 11 11
      pkg/database/ent/event_create.go
  52. 5 6
      pkg/database/ent/event_delete.go
  53. 78 65
      pkg/database/ent/event_query.go
  54. 28 29
      pkg/database/ent/event_update.go
  55. 10 5
      pkg/database/ent/hook/hook.go
  56. 85 68
      pkg/database/ent/machine.go
  57. 3 3
      pkg/database/ent/machine/machine.go
  58. 3 3
      pkg/database/ent/machine/where.go
  59. 19 19
      pkg/database/ent/machine_create.go
  60. 5 6
      pkg/database/ent/machine_delete.go
  61. 79 62
      pkg/database/ent/machine_query.go
  62. 54 55
      pkg/database/ent/machine_update.go
  63. 61 53
      pkg/database/ent/meta.go
  64. 2 2
      pkg/database/ent/meta/meta.go
  65. 3 3
      pkg/database/ent/meta/where.go
  66. 11 11
      pkg/database/ent/meta_create.go
  67. 5 6
      pkg/database/ent/meta_delete.go
  68. 78 65
      pkg/database/ent/meta_query.go
  69. 28 29
      pkg/database/ent/meta_update.go
  70. 2 0
      pkg/database/ent/migrate/migrate.go
  71. 4 1
      pkg/database/ent/migrate/schema.go
  72. 163 179
      pkg/database/ent/mutation.go
  73. 0 355
      pkg/database/ent/privacy/privacy.go
  74. 3 3
      pkg/database/ent/runtime.go
  75. 2 2
      pkg/database/ent/runtime/runtime.go
  76. 3 0
      pkg/database/ent/schema/decision.go
  77. 0 6
      pkg/models/decision.go
  78. 0 6
      pkg/models/localapi_swagger.yaml
  79. 106 0
      pkg/types/ip.go
  80. 220 0
      pkg/types/ip_test.go
  81. 0 37
      pkg/types/utils.go
  82. 434 0
      scripts/test_ip_management.sh
  83. 1 1
      scripts/test_wizard_upgrade.sh

+ 1 - 1
.github/workflows/ci_functests-install.yml

@@ -53,7 +53,7 @@ jobs:
       run: |
         sudo cscli decisions list
         sudo cscli decisions list -o=json | jq -e '.[].decisions[0].value == "1.1.1.172"'
-        sudo cscli decisions list -r 1.1.1.0/24 -o=json | jq -e '.[].decisions[0].value == "1.1.1.172"'
+        sudo cscli decisions list -r 1.1.1.0/24 -o=json --contained | jq -e '.[].decisions[0].value == "1.1.1.172"'
         sudo cscli decisions list -r 1.1.2.0/24 -o=json | jq -e '. == null'
         sudo cscli decisions list -i 1.1.1.172 -o=json | jq -e '.[].decisions[0].value == "1.1.1.172"'
         sudo cscli decisions list -i 1.1.1.173 -o=json | jq -e '. == null'

+ 31 - 0
.github/workflows/ci_functests-ip_mgmt.yml

@@ -0,0 +1,31 @@
+name: Functionnal tests for IP management
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  build:
+    name: Install generated release and perform basic tests
+    runs-on: ubuntu-latest
+    steps:
+    - name: Set up Go 1.13
+      uses: actions/setup-go@v1
+      with:
+        go-version: 1.13
+      id: go
+    - name: Check out code into the Go module directory
+      uses: actions/checkout@v2
+    - name: install ipset for bouncer
+      run: |
+        sudo apt update
+        sudo apt install jq
+    - name: run tests
+      run: |
+        cd scripts/
+        sudo ./test_ip_management.sh
+

+ 7 - 0
cmd/crowdsec-cli/alerts.go

@@ -21,6 +21,7 @@ import (
 )
 
 var printMachine bool
+var contained bool
 var limit *int
 
 func DecisionsFromAlert(alert *models.Alert) string {
@@ -232,6 +233,7 @@ func NewAlertsCmd() *cobra.Command {
 		Since:          new(string),
 		Until:          new(string),
 		TypeEquals:     new(string),
+		Contains:       new(bool),
 	}
 	limit = new(int)
 	var cmdAlertsList = &cobra.Command{
@@ -300,6 +302,7 @@ cscli alerts list --type ban`,
 			if *alertListFilter.RangeEquals == "" {
 				alertListFilter.RangeEquals = nil
 			}
+			*alertListFilter.Contains = !contained
 			alerts, _, err := Client.Alerts.List(context.Background(), alertListFilter)
 			if err != nil {
 				log.Fatalf("Unable to list alerts : %v", err.Error())
@@ -320,6 +323,7 @@ cscli alerts list --type ban`,
 	cmdAlertsList.Flags().StringVar(alertListFilter.TypeEquals, "type", "", "restrict to alerts with given decision type (ie. ban, captcha)")
 	cmdAlertsList.Flags().StringVar(alertListFilter.ScopeEquals, "scope", "", "restrict to alerts of this scope (ie. ip,range)")
 	cmdAlertsList.Flags().StringVarP(alertListFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
+	cmdAlertsList.Flags().BoolVar(&contained, "contained", false, "query decisions contained by range")
 	cmdAlertsList.Flags().BoolVarP(&printMachine, "machine", "m", false, "print machines that sended alerts")
 	cmdAlertsList.Flags().IntVarP(limit, "limit", "l", 50, "limit size of alerts list table (0 to view all alerts)")
 	cmdAlerts.AddCommand(cmdAlertsList)
@@ -332,6 +336,7 @@ cscli alerts list --type ban`,
 		ScenarioEquals: new(string),
 		IPEquals:       new(string),
 		RangeEquals:    new(string),
+		Contains:       new(bool),
 	}
 	var cmdAlertsDelete = &cobra.Command{
 		Use: "delete [filters] [--all]",
@@ -380,6 +385,7 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`,
 				if *alertDeleteFilter.RangeEquals == "" {
 					alertDeleteFilter.RangeEquals = nil
 				}
+				*alertDeleteFilter.Contains = !contained
 			} else {
 				alertDeleteFilter = apiclient.AlertsDeleteOpts{}
 			}
@@ -398,6 +404,7 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`,
 	cmdAlertsDelete.Flags().StringVarP(alertDeleteFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
 	cmdAlertsDelete.Flags().StringVarP(alertDeleteFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
 	cmdAlertsDelete.Flags().BoolVarP(&AlertDeleteAll, "all", "a", false, "delete all alerts")
+	cmdAlertsDelete.Flags().BoolVar(&contained, "contained", false, "query decisions contained by range")
 
 	cmdAlerts.AddCommand(cmdAlertsDelete)
 

+ 8 - 19
cmd/crowdsec-cli/decisions.go

@@ -12,7 +12,6 @@ import (
 
 	"github.com/crowdsecurity/crowdsec/pkg/apiclient"
 	"github.com/crowdsecurity/crowdsec/pkg/cwversion"
-	"github.com/crowdsecurity/crowdsec/pkg/database"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
 	"github.com/go-openapi/strfmt"
@@ -140,6 +139,7 @@ func NewDecisionsCmd() *cobra.Command {
 		Until:          new(string),
 		TypeEquals:     new(string),
 		IncludeCAPI:    new(bool),
+		Contains:       new(bool),
 	}
 	NoSimu := new(bool)
 	var cmdDecisionsList = &cobra.Command{
@@ -209,6 +209,7 @@ cscli decisions list -t ban
 			if *filter.RangeEquals == "" {
 				filter.RangeEquals = nil
 			}
+			*filter.Contains = !contained
 			alerts, _, err := Client.Alerts.List(context.Background(), filter)
 			if err != nil {
 				log.Fatalf("Unable to list decisions : %v", err.Error())
@@ -231,6 +232,8 @@ cscli decisions list -t ban
 	cmdDecisionsList.Flags().StringVarP(filter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
 	cmdDecisionsList.Flags().StringVarP(filter.RangeEquals, "range", "r", "", "restrict to alerts from this source range (shorthand for --scope range --value <RANGE>)")
 	cmdDecisionsList.Flags().BoolVar(NoSimu, "no-simu", false, "exclude decisions in simulation mode")
+	cmdDecisionsList.Flags().BoolVar(&contained, "contained", false, "query decisions contained by range")
+
 	cmdDecisions.AddCommand(cmdDecisionsList)
 
 	var (
@@ -254,7 +257,6 @@ cscli decisions add --scope username --value foobar
 		/*TBD : fix long and example*/
 		Args: cobra.ExactArgs(0),
 		Run: func(cmd *cobra.Command, args []string) {
-			var startIP, endIP int64
 			var err error
 			var ip, ipRange string
 			alerts := models.AddAlertsRequest{}
@@ -285,20 +287,6 @@ cscli decisions add --scope username --value foobar
 				return
 			}
 
-			if addScope == types.Ip {
-				startIP, endIP, err = database.GetIpsFromIpRange(addValue + "/32")
-				if err != nil {
-					log.Fatalf("unable to parse IP : '%s'", addValue)
-				}
-			}
-			if addScope == types.Range {
-				startIP, endIP, err = database.GetIpsFromIpRange(addValue)
-				if err != nil {
-					log.Fatalf("unable to parse Range : '%s'", addValue)
-				}
-				ipRange = addValue
-			}
-
 			if addReason == "" {
 				addReason = fmt.Sprintf("manual '%s' from '%s'", addType, csConfig.API.Client.Credentials.Login)
 			}
@@ -310,8 +298,6 @@ cscli decisions add --scope username --value foobar
 				Type:     &addType,
 				Scenario: &addReason,
 				Origin:   &origin,
-				StartIP:  startIP,
-				EndIP:    endIP,
 			}
 			alert := models.Alert{
 				Capacity:        &capacity,
@@ -364,6 +350,7 @@ cscli decisions add --scope username --value foobar
 		TypeEquals:  new(string),
 		IPEquals:    new(string),
 		RangeEquals: new(string),
+		Contains:    new(bool),
 	}
 	var delDecisionId string
 	var delDecisionAll bool
@@ -414,7 +401,7 @@ cscli decisions delete --type captcha
 			if *delFilter.RangeEquals == "" {
 				delFilter.RangeEquals = nil
 			}
-
+			*delFilter.Contains = !contained
 			if delDecisionId == "" {
 				decisions, _, err = Client.Decisions.Delete(context.Background(), delFilter)
 				if err != nil {
@@ -437,6 +424,8 @@ cscli decisions delete --type captcha
 	cmdDecisionsDelete.Flags().StringVarP(delFilter.TypeEquals, "type", "t", "", "the decision type (ie. ban,captcha)")
 	cmdDecisionsDelete.Flags().StringVarP(delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
 	cmdDecisionsDelete.Flags().BoolVar(&delDecisionAll, "all", false, "delete all decisions")
+	cmdDecisionsDelete.Flags().BoolVar(&contained, "contained", false, "query decisions contained by range")
+
 	cmdDecisions.AddCommand(cmdDecisionsDelete)
 
 	return cmdDecisions

+ 4 - 4
go.mod

@@ -18,7 +18,7 @@ require (
 	github.com/docker/docker v20.10.2+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/enescakir/emoji v1.0.0
-	github.com/facebook/ent v0.5.0
+	github.com/facebook/ent v0.5.4
 	github.com/gin-gonic/gin v1.6.3
 	github.com/go-co-op/gocron v0.3.3
 	github.com/go-openapi/analysis v0.19.12 // indirect
@@ -35,7 +35,7 @@ require (
 	github.com/google/go-querystring v1.0.0
 	github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e
 	github.com/hashicorp/go-version v1.2.1
-	github.com/lib/pq v1.8.0
+	github.com/lib/pq v1.9.0
 	github.com/logrusorgru/grokky v0.0.0-20180829062225-47edf017d42c
 	github.com/mailru/easyjson v0.7.6 // indirect
 	github.com/mattn/go-sqlite3 v2.0.3+incompatible
@@ -56,11 +56,11 @@ require (
 	github.com/sirupsen/logrus v1.7.0
 	github.com/spf13/cobra v1.1.1
 	github.com/stretchr/testify v1.6.1
-	github.com/ugorji/go/codec v1.2.0 // indirect
+	github.com/ugorji/go v1.2.0 // indirect
 	github.com/vjeantet/grok v1.0.1 // indirect
 	go.mongodb.org/mongo-driver v1.4.3 // indirect
 	golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582
-	golang.org/x/mod v0.3.0
+	golang.org/x/mod v0.4.0
 	golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
 	golang.org/x/sys v0.0.0-20201116161645-c061ba923fbb
 	golang.org/x/text v0.3.4 // indirect

+ 17 - 13
go.sum

@@ -135,8 +135,8 @@ github.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkK
 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/facebook/ent v0.5.0 h1:NlDQDxJi1X6+20CCjRQgu8UqvRhQNm5ocPBCQYdxC/8=
-github.com/facebook/ent v0.5.0/go.mod h1:HrrMNGsvgZoGQ74PGBQJ9r9WNOVMqKQefcOJFXuOUlw=
+github.com/facebook/ent v0.5.4 h1:kIf2BQUdRJ7XrlTXzCyJCg69ar1K1FjFR2UQWRo/M8M=
+github.com/facebook/ent v0.5.4/go.mod h1:ZioHzZjDTB/uPABl7pff/v2+cdsEBca8roSTWW3/UaE=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
@@ -151,6 +151,7 @@ github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-bindata/go-bindata v1.0.1-0.20190711162640-ee3c2418e368 h1:WNHfSP1q2vuAa9vF54RrhCl4nqxCjVcXhlbsRXbGOSY=
 github.com/go-bindata/go-bindata v1.0.1-0.20190711162640-ee3c2418e368/go.mod h1:7xCgX1lzlrXPHkfvn3EhumqHkmSlzt8at9q7v0ax19c=
 github.com/go-co-op/gocron v0.3.3 h1:QnarcMZWWKrEP25uCbtDiLsnnGw+PhCjL3wNITdWJOs=
 github.com/go-co-op/gocron v0.3.3/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M=
@@ -180,6 +181,7 @@ github.com/go-openapi/errors v0.19.7 h1:Lcq+o0mSwCLKACMxZhreVHigB9ebghJ/lrmeaqAS
 github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
 github.com/go-openapi/errors v0.19.8 h1:doM+tQdZbUm9gydV9yR+iQNmztbjj7I3sW4sIcAwIzc=
 github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
+github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
 github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
 github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
@@ -336,8 +338,8 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
-github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0=
+github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e h1:XmA6L9IPRdUr28a+SK/oMchGgQy159wvzXA5tJ7l+40=
@@ -429,8 +431,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
-github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
+github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
 github.com/logrusorgru/grokky v0.0.0-20180829062225-47edf017d42c h1:S3P1IbG7Z7V2p9juEttr1oRwozZd2kxw+RQiYBYB1wQ=
@@ -464,7 +466,7 @@ github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuuj
 github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-sqlite3 v1.14.4/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@@ -482,8 +484,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
 github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
-github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
+github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -737,6 +739,8 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -765,8 +769,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
 golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -779,7 +782,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -871,7 +874,8 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20210105164027-a548c3f4af2d h1:v9TQ4+tS+0r4R+9E6svkcl6ocSxeHONeVkK2y6YhzmA=
+golang.org/x/tools v0.0.0-20210105164027-a548c3f4af2d/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=

+ 2 - 0
pkg/apiclient/alerts_service.go

@@ -26,6 +26,7 @@ type AlertsListOpts struct {
 	ActiveDecisionEquals *bool   `url:"has_active_decision,omitempty"`
 	IncludeCAPI          *bool   `url:"include_capi,omitempty"`
 	Limit                *int    `url:"limit,omitempty"`
+	Contains             *bool   `url:"contains,omitempty"`
 	ListOpts
 }
 
@@ -39,6 +40,7 @@ type AlertsDeleteOpts struct {
 	Until                *string `url:"until,omitempty"`
 	ActiveDecisionEquals *bool   `url:"has_active_decision,omitempty"`
 	SourceEquals         *string `url:"alert_source,omitempty"`
+	Contains             *bool   `url:"contains,omitempty"`
 	ListOpts
 }
 

+ 0 - 6
pkg/apiclient/alerts_service_test.go

@@ -56,13 +56,11 @@ func TestAlertsListAsMachine(t *testing.T) {
 			{"capacity":5,"created_at":"2020-11-28T10:20:47+01:00",
 			 "decisions":[
 				  {"duration":"59m49.264032632s",
-				  "end_ip":16843180,
 				  "id":1,
 				  "origin":"crowdsec",
 				  "scenario":"crowdsecurity/ssh-bf",
 				  "scope":"Ip",
 				  "simulated":false,
-				  "start_ip":16843180,
 				  "type":"ban",
 				  "value":"1.1.1.172"}
 				  ],
@@ -127,14 +125,12 @@ func TestAlertsListAsMachine(t *testing.T) {
 			Decisions: []*models.Decision{
 				&models.Decision{
 					Duration: &tduration,
-					EndIP:    16843180,
 					ID:       1,
 					Origin:   &torigin,
 					Scenario: &tscenario,
 
 					Scope:     &tscope,
 					Simulated: new(bool), //false,
-					StartIP:   16843180,
 					Type:      &ttype,
 					Value:     &tvalue,
 				},
@@ -328,14 +324,12 @@ func TestAlertsGetAsMachine(t *testing.T) {
 		Decisions: []*models.Decision{
 			&models.Decision{
 				Duration: &tduration,
-				EndIP:    16843180,
 				ID:       1,
 				Origin:   &torigin,
 				Scenario: &tscenario,
 
 				Scope:     &tscope,
 				Simulated: new(bool), //false,
-				StartIP:   16843180,
 				Type:      &ttype,
 				Value:     &tvalue,
 			},

+ 3 - 0
pkg/apiclient/decisions_service.go

@@ -16,6 +16,8 @@ type DecisionsListOpts struct {
 	TypeEquals  *string `url:"type,omitempty"`
 	IPEquals    *string `url:"ip,omitempty"`
 	RangeEquals *string `url:"range,omitempty"`
+	Contains    *bool   `url:"contains,omitempty"`
+
 	ListOpts
 }
 
@@ -25,6 +27,7 @@ type DecisionsDeleteOpts struct {
 	TypeEquals  *string `url:"type,omitempty"`
 	IPEquals    *string `url:"ip,omitempty"`
 	RangeEquals *string `url:"range,omitempty"`
+	Contains    *bool   `url:"contains,omitempty"`
 	ListOpts
 }
 

+ 2 - 6
pkg/apiclient/decisions_service_test.go

@@ -26,7 +26,7 @@ func TestDecisionsList(t *testing.T) {
 			assert.Equal(t, r.URL.RawQuery, "ip=1.2.3.4")
 			assert.Equal(t, r.Header.Get("X-Api-Key"), "ixu")
 			w.WriteHeader(http.StatusOK)
-			w.Write([]byte(`[{"duration":"3h59m55.756182786s","end_ip":16909060,"id":4,"origin":"cscli","scenario":"manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'","scope":"Ip","start_ip":16909060,"type":"ban","value":"1.2.3.4"}]`))
+			w.Write([]byte(`[{"duration":"3h59m55.756182786s","id":4,"origin":"cscli","scenario":"manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'","scope":"Ip","type":"ban","value":"1.2.3.4"}]`))
 		} else {
 			w.WriteHeader(http.StatusOK)
 			w.Write([]byte(`null`))
@@ -57,12 +57,10 @@ func TestDecisionsList(t *testing.T) {
 	expected := &models.GetDecisionsResponse{
 		&models.Decision{
 			Duration: &tduration,
-			EndIP:    16909060,
 			ID:       4,
 			Origin:   &torigin,
 			Scenario: &tscenario,
 			Scope:    &tscope,
-			StartIP:  16909060,
 			Type:     &ttype,
 			Value:    &tvalue,
 		},
@@ -110,7 +108,7 @@ func TestDecisionsStream(t *testing.T) {
 
 			if r.URL.RawQuery == "startup=true" {
 				w.WriteHeader(http.StatusOK)
-				w.Write([]byte(`{"deleted":null,"new":[{"duration":"3h59m55.756182786s","end_ip":16909060,"id":4,"origin":"cscli","scenario":"manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'","scope":"Ip","start_ip":16909060,"type":"ban","value":"1.2.3.4"}]}`))
+				w.Write([]byte(`{"deleted":null,"new":[{"duration":"3h59m55.756182786s","id":4,"origin":"cscli","scenario":"manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'","scope":"Ip","type":"ban","value":"1.2.3.4"}]}`))
 			} else {
 				w.WriteHeader(http.StatusOK)
 				w.Write([]byte(`{"deleted":null,"new":null}`))
@@ -150,12 +148,10 @@ func TestDecisionsStream(t *testing.T) {
 		New: models.GetDecisionsResponse{
 			&models.Decision{
 				Duration: &tduration,
-				EndIP:    16909060,
 				ID:       4,
 				Origin:   &torigin,
 				Scenario: &tscenario,
 				Scope:    &tscope,
-				StartIP:  16909060,
 				Type:     &ttype,
 				Value:    &tvalue,
 			},

+ 15 - 15
pkg/apiserver/alerts_test.go

@@ -185,7 +185,7 @@ func TestAlertListFilters(t *testing.T) {
 	assert.Equal(t, 200, w.Code)
 	//check alert and decision
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test decision_type filter (ok)
 	w = httptest.NewRecorder()
@@ -195,7 +195,7 @@ func TestAlertListFilters(t *testing.T) {
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test decision_type filter (bad value)
 	w = httptest.NewRecorder()
@@ -214,7 +214,7 @@ func TestAlertListFilters(t *testing.T) {
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test scope (bad value)
 	w = httptest.NewRecorder()
@@ -233,7 +233,7 @@ func TestAlertListFilters(t *testing.T) {
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test scenario (bad value)
 	w = httptest.NewRecorder()
@@ -252,7 +252,7 @@ func TestAlertListFilters(t *testing.T) {
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test ip (bad value)
 	w = httptest.NewRecorder()
@@ -270,21 +270,21 @@ func TestAlertListFilters(t *testing.T) {
 	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token))
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 500, w.Code)
-	assert.Equal(t, `{"message":"unable to parse 'gruueq': %!s(\u003cnil\u003e): invalid ip address / range"}`, w.Body.String())
+	assert.Equal(t, `{"message":"unable to convert 'gruueq' to int: invalid address: invalid ip address / range"}`, w.Body.String())
 
 	//test range (ok)
 	w = httptest.NewRecorder()
-	req, _ = http.NewRequest("GET", "/v1/alerts?range=91.121.79.0/24", nil)
+	req, _ = http.NewRequest("GET", "/v1/alerts?range=91.121.79.0/24&contains=false", nil)
 	req.Header.Add("User-Agent", UserAgent)
 	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token))
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test range
 	w = httptest.NewRecorder()
-	req, _ = http.NewRequest("GET", "/v1/alerts?range=99.122.77.0/24", nil)
+	req, _ = http.NewRequest("GET", "/v1/alerts?range=99.122.77.0/24&contains=false", nil)
 	req.Header.Add("User-Agent", UserAgent)
 	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token))
 	router.ServeHTTP(w, req)
@@ -298,7 +298,7 @@ func TestAlertListFilters(t *testing.T) {
 	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token))
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 500, w.Code)
-	assert.Equal(t, `{"message":"unable to convert 'ratata' to int interval: 'ratata' is not a valid CIDR: invalid ip address / range"}`, w.Body.String())
+	assert.Equal(t, `{"message":"unable to convert 'ratata' to int: invalid address: invalid ip address / range"}`, w.Body.String())
 
 	//test since (ok)
 	w = httptest.NewRecorder()
@@ -308,7 +308,7 @@ func TestAlertListFilters(t *testing.T) {
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test since (ok but yelds no results)
 	w = httptest.NewRecorder()
@@ -336,7 +336,7 @@ func TestAlertListFilters(t *testing.T) {
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test until (ok but no return)
 	w = httptest.NewRecorder()
@@ -364,7 +364,7 @@ func TestAlertListFilters(t *testing.T) {
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test simulated (ok)
 	w = httptest.NewRecorder()
@@ -374,7 +374,7 @@ func TestAlertListFilters(t *testing.T) {
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test has active decision
 	w = httptest.NewRecorder()
@@ -384,7 +384,7 @@ func TestAlertListFilters(t *testing.T) {
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
 	assert.Contains(t, w.Body.String(), "Ip 91.121.79.195 performed 'crowdsecurity/ssh-bf' (6 events over ")
-	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"start_ip":1534676931,"type":"ban","value":"91.121.79.195"`)
+	assert.Contains(t, w.Body.String(), `scope":"Ip","simulated":false,"type":"ban","value":"91.121.79.195"`)
 
 	//test has active decision
 	w = httptest.NewRecorder()

+ 14 - 13
pkg/apiserver/apic.go

@@ -9,7 +9,6 @@ import (
 	"time"
 
 	"github.com/crowdsecurity/crowdsec/pkg/apiclient"
-	"github.com/crowdsecurity/crowdsec/pkg/apiserver/controllers"
 	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
 	"github.com/crowdsecurity/crowdsec/pkg/cwversion"
 	"github.com/crowdsecurity/crowdsec/pkg/database"
@@ -269,28 +268,30 @@ func (a *apic) PullTop() error {
 
 	// process new decisions
 	for _, decision := range data.New {
-		/*ensure scope makes sense no matter what consensus gives*/
-		if strings.ToLower(*decision.Scope) == "ip" {
-			*decision.Scope = types.Ip
-		} else if strings.ToLower(*decision.Scope) == "range" {
-			*decision.Scope = types.Range
+		var start_ip, start_sfx, end_ip, end_sfx int64
+		var sz int
+
+		/*if the scope is IP or Range, convert the value to integers */
+		if strings.ToLower(*decision.Scope) == "ip" || strings.ToLower(*decision.Scope) == "range" {
+			sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(*decision.Value)
+			if err != nil {
+				return errors.Wrapf(err, "invalid ip/range %s", *decision.Value)
+			}
 		}
 
 		duration, err := time.ParseDuration(*decision.Duration)
 		if err != nil {
 			return errors.Wrapf(err, "parse decision duration '%s':", *decision.Duration)
 		}
-		startIP, endIP, err := controllers.GetIpsFromIpRange(*decision.Value)
-		if err != nil {
-			return errors.Wrapf(err, "ip to int '%s':", *decision.Value)
-		}
-
 		_, err = a.dbClient.Ent.Decision.Create().
 			SetUntil(time.Now().Add(duration)).
 			SetScenario(*decision.Scenario).
 			SetType(*decision.Type).
-			SetStartIP(startIP).
-			SetEndIP(endIP).
+			SetIPSize(int64(sz)).
+			SetStartIP(start_ip).
+			SetStartSuffix(start_sfx).
+			SetEndIP(end_ip).
+			SetEndSuffix(end_sfx).
 			SetValue(*decision.Value).
 			SetScope(*decision.Scope).
 			SetOrigin(*decision.Origin).

+ 0 - 65
pkg/apiserver/controllers/utils.go

@@ -1,65 +0,0 @@
-package controllers
-
-import (
-	"encoding/binary"
-	"fmt"
-	"net"
-)
-
-func IP2Int(ip net.IP) uint32 {
-	if len(ip) == 16 {
-		return binary.BigEndian.Uint32(ip[12:16])
-	}
-	return binary.BigEndian.Uint32(ip)
-}
-
-func Int2ip(nn uint32) net.IP {
-	ip := make(net.IP, 4)
-	binary.BigEndian.PutUint32(ip, nn)
-	return ip
-}
-
-func IsIpv4(host string) bool {
-	return net.ParseIP(host) != nil
-}
-
-//Stolen from : https://github.com/llimllib/ipaddress/
-// Return the final address of a net range. Convert to IPv4 if possible,
-// otherwise return an ipv6
-func LastAddress(n *net.IPNet) net.IP {
-	ip := n.IP.To4()
-	if ip == nil {
-		ip = n.IP
-		return net.IP{
-			ip[0] | ^n.Mask[0], ip[1] | ^n.Mask[1], ip[2] | ^n.Mask[2],
-			ip[3] | ^n.Mask[3], ip[4] | ^n.Mask[4], ip[5] | ^n.Mask[5],
-			ip[6] | ^n.Mask[6], ip[7] | ^n.Mask[7], ip[8] | ^n.Mask[8],
-			ip[9] | ^n.Mask[9], ip[10] | ^n.Mask[10], ip[11] | ^n.Mask[11],
-			ip[12] | ^n.Mask[12], ip[13] | ^n.Mask[13], ip[14] | ^n.Mask[14],
-			ip[15] | ^n.Mask[15]}
-	}
-
-	return net.IPv4(
-		ip[0]|^n.Mask[0],
-		ip[1]|^n.Mask[1],
-		ip[2]|^n.Mask[2],
-		ip[3]|^n.Mask[3])
-}
-
-func GetIpsFromIpRange(host string) (int64, int64, error) {
-	var ipStart int64
-	var ipEnd int64
-	var err error
-	var parsedRange *net.IPNet
-
-	if _, parsedRange, err = net.ParseCIDR(host); err != nil {
-		return ipStart, ipEnd, fmt.Errorf("'%s' is not a valid CIDR", host)
-	}
-	if parsedRange == nil {
-		return ipStart, ipEnd, fmt.Errorf("unable to parse network : %s", err)
-	}
-	ipStart = int64(IP2Int(parsedRange.IP))
-	ipEnd = int64(IP2Int(LastAddress(parsedRange)))
-
-	return ipStart, ipEnd, nil
-}

+ 1 - 11
pkg/apiserver/controllers/v1/alerts.go

@@ -78,8 +78,6 @@ func FormatOneAlert(alert *ent.Alert) *models.Alert {
 			Duration:  &duration, // transform into time.Time ?
 			Scenario:  &decisionItem.Scenario,
 			Type:      &decisionItem.Type,
-			StartIP:   decisionItem.StartIP,
-			EndIP:     decisionItem.EndIP,
 			Scope:     &decisionItem.Scope,
 			Value:     &decisionItem.Value,
 			Origin:    &decisionItem.Origin,
@@ -119,15 +117,7 @@ func (c *Controller) CreateAlert(gctx *gin.Context) {
 	}
 
 	for _, alert := range input {
-		if len(alert.Decisions) > 0 {
-			log.Debugf("alert %s already has decisions, don't apply profiles", *alert.Message)
-			for _, decision := range alert.Decisions {
-				if decision.EndIP != 0 && decision.StartIP != 0 && decision.EndIP < decision.StartIP {
-					gctx.JSON(http.StatusBadRequest, gin.H{"message": "'start_ip' must be lower than or equal to 'end_ip' in decisions"})
-					return
-				}
-			}
-		} else {
+		if len(alert.Decisions) == 0 {
 			decisions, err := csprofiles.EvaluateProfiles(c.Profiles, alert)
 			if err != nil {
 				gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})

+ 0 - 2
pkg/apiserver/controllers/v1/decisions.go

@@ -21,8 +21,6 @@ func FormatDecisions(decisions []*ent.Decision) ([]*models.Decision, error) {
 		decision := models.Decision{
 			ID:       int64(dbDecision.ID),
 			Duration: &duration,
-			EndIP:    dbDecision.EndIP,
-			StartIP:  dbDecision.StartIP,
 			Scenario: &dbDecision.Scenario,
 			Scope:    &dbDecision.Scope,
 			Value:    &dbDecision.Value,

+ 14 - 14
pkg/apiserver/decisions_test.go

@@ -58,7 +58,7 @@ func TestDeleteDecisionRange(t *testing.T) {
 
 	// delete by range
 	w = httptest.NewRecorder()
-	req, _ = http.NewRequest("DELETE", "/v1/decisions?range=91.121.79.0/24", strings.NewReader(""))
+	req, _ = http.NewRequest("DELETE", "/v1/decisions?range=91.121.79.0/24&contains=false", strings.NewReader(""))
 	req.Header.Add("User-Agent", UserAgent)
 	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token))
 	router.ServeHTTP(w, req)
@@ -179,8 +179,8 @@ func TestGetDecisionFilters(t *testing.T) {
 	req.Header.Add("X-Api-Key", APIKey)
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
-	assert.Contains(t, w.Body.String(), `"end_ip":1534676915,"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","start_ip":1534676915,"type":"ban","value":"91.121.79.179"`)
-	assert.Contains(t, w.Body.String(), `"end_ip":1534676914,"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","start_ip":1534676914,"type":"ban","value":"91.121.79.178"`)
+	assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
+	assert.Contains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
 
 	// Get Decision : type filter
 	w = httptest.NewRecorder()
@@ -189,8 +189,8 @@ func TestGetDecisionFilters(t *testing.T) {
 	req.Header.Add("X-Api-Key", APIKey)
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
-	assert.Contains(t, w.Body.String(), `"end_ip":1534676915,"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","start_ip":1534676915,"type":"ban","value":"91.121.79.179"`)
-	assert.Contains(t, w.Body.String(), `"end_ip":1534676914,"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","start_ip":1534676914,"type":"ban","value":"91.121.79.178"`)
+	assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
+	assert.Contains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
 
 	// Get Decision : scope/value
 	w = httptest.NewRecorder()
@@ -199,8 +199,8 @@ func TestGetDecisionFilters(t *testing.T) {
 	req.Header.Add("X-Api-Key", APIKey)
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
-	assert.Contains(t, w.Body.String(), `"end_ip":1534676915,"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","start_ip":1534676915,"type":"ban","value":"91.121.79.179"`)
-	assert.NotContains(t, w.Body.String(), `"end_ip":1534676914,"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","start_ip":1534676914,"type":"ban","value":"91.121.79.178"`)
+	assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
+	assert.NotContains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
 
 	// Get Decision : ip filter
 	w = httptest.NewRecorder()
@@ -209,18 +209,18 @@ func TestGetDecisionFilters(t *testing.T) {
 	req.Header.Add("X-Api-Key", APIKey)
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
-	assert.Contains(t, w.Body.String(), `"end_ip":1534676915,"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","start_ip":1534676915,"type":"ban","value":"91.121.79.179"`)
-	assert.NotContains(t, w.Body.String(), `"end_ip":1534676914,"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","start_ip":1534676914,"type":"ban","value":"91.121.79.178"`)
+	assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
+	assert.NotContains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
 
 	// Get decision : by range
 	w = httptest.NewRecorder()
-	req, _ = http.NewRequest("GET", "/v1/decisions?range=91.121.79.0/24", strings.NewReader(""))
+	req, _ = http.NewRequest("GET", "/v1/decisions?range=91.121.79.0/24&contains=false", strings.NewReader(""))
 	req.Header.Add("User-Agent", UserAgent)
 	req.Header.Add("X-Api-Key", APIKey)
 	router.ServeHTTP(w, req)
 	assert.Equal(t, 200, w.Code)
-	assert.Contains(t, w.Body.String(), `"end_ip":1534676915,"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","start_ip":1534676915,"type":"ban","value":"91.121.79.179"`)
-	assert.Contains(t, w.Body.String(), `"end_ip":1534676914,"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","start_ip":1534676914,"type":"ban","value":"91.121.79.178"`)
+	assert.Contains(t, w.Body.String(), `"id":1,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.179"`)
+	assert.Contains(t, w.Body.String(), `"id":2,"origin":"crowdsec","scenario":"crowdsecurity/ssh-bf","scope":"Ip","type":"ban","value":"91.121.79.178"`)
 }
 
 func TestGetDecision(t *testing.T) {
@@ -277,7 +277,7 @@ func TestGetDecision(t *testing.T) {
 	router.ServeHTTP(w, req)
 
 	assert.Equal(t, 200, w.Code)
-	assert.Contains(t, w.Body.String(), "\"end_ip\":2130706433,\"id\":1,\"origin\":\"test\",\"scenario\":\"crowdsecurity/test\",\"scope\":\"ip\",\"start_ip\":2130706433,\"type\":\"ban\",\"value\":\"127.0.0.1\"}]")
+	assert.Contains(t, w.Body.String(), "\"id\":1,\"origin\":\"test\",\"scenario\":\"crowdsecurity/test\",\"scope\":\"ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"}]")
 
 }
 
@@ -449,5 +449,5 @@ func TestStreamDecision(t *testing.T) {
 	router.ServeHTTP(w, req)
 
 	assert.Equal(t, 200, w.Code)
-	assert.Contains(t, w.Body.String(), "\"end_ip\":2130706433,\"id\":1,\"origin\":\"test\",\"scenario\":\"crowdsecurity/test\",\"scope\":\"ip\",\"start_ip\":2130706433,\"type\":\"ban\",\"value\":\"127.0.0.1\"}]}")
+	assert.Contains(t, w.Body.String(), "\"id\":1,\"origin\":\"test\",\"scenario\":\"crowdsecurity/test\",\"scope\":\"ip\",\"type\":\"ban\",\"value\":\"127.0.0.1\"}]}")
 }

+ 0 - 2
pkg/apiserver/tests/alertWithInvalidMachineID_sample.json

@@ -8,8 +8,6 @@
             {
                 "id": 1,
                 "duration": "1h",
-                "start_ip": 2130706433,
-                "end_ip": 2130706433,
                 "origin": "test",
                 "scenario": "crowdsecurity/test",
                 "scope": "ip",

+ 0 - 2
pkg/apiserver/tests/alert_sample.json

@@ -8,8 +8,6 @@
             {
                 "id": 1,
                 "duration": "1h",
-                "start_ip": 2130706433,
-                "end_ip": 2130706433,
                 "origin": "test",
                 "scenario": "crowdsecurity/test",
                 "scope": "ip",

+ 0 - 2
pkg/apiserver/tests/invalidAlert_sample.json

@@ -7,8 +7,6 @@
             {
                 "id": 1,
                 "duration": "1h",
-                "start_ip": 2130706433,
-                "end_ip": 2130706433,
                 "origin": "test",
                 "scenario": "crowdsecurity/test",
                 "scope": "ip",

+ 0 - 54
pkg/apiserver/utils_test.go

@@ -1,54 +0,0 @@
-package apiserver
-
-import (
-	"net"
-	"testing"
-
-	"github.com/crowdsecurity/crowdsec/pkg/apiserver/controllers"
-	log "github.com/sirupsen/logrus"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestIP2Int(t *testing.T) {
-	ipInt := controllers.IP2Int(net.ParseIP("127.0.0.1"))
-	assert.Equal(t, uint32(2130706433), ipInt)
-
-	ipInt = controllers.IP2Int([]byte{127, 0, 0, 1})
-	assert.Equal(t, uint32(2130706433), ipInt)
-}
-
-func TestInt2IP(t *testing.T) {
-	IP := controllers.Int2ip(uint32(2130706433))
-	assert.Equal(t, "127.0.0.1", IP.String())
-}
-
-func TestIsIPv4(t *testing.T) {
-	IsIpv4 := controllers.IsIpv4("127.0.0.1")
-	assert.Equal(t, true, IsIpv4)
-
-	IsIpv4 = controllers.IsIpv4("127.0.0")
-	assert.Equal(t, false, IsIpv4)
-}
-
-func TestLastAddress(t *testing.T) {
-	_, ipv4Net, err := net.ParseCIDR("192.168.0.1/24")
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	lastAddress := controllers.LastAddress(ipv4Net)
-	assert.Equal(t, "192.168.0.255", lastAddress.String())
-}
-
-func TestGetIpsFromIpRange(t *testing.T) {
-	IPStart, IPEnd, err := controllers.GetIpsFromIpRange("192.168.0.1/65")
-	assert.Equal(t, "'192.168.0.1/65' is not a valid CIDR", err.Error())
-	assert.Equal(t, int64(0), IPStart)
-	assert.Equal(t, int64(0), IPEnd)
-
-	IPStart, IPEnd, err = controllers.GetIpsFromIpRange("192.168.0.1/24")
-	assert.Equal(t, nil, err)
-	assert.Equal(t, int64(3232235520), IPStart)
-	assert.Equal(t, int64(3232235775), IPEnd)
-}

+ 0 - 33
pkg/csprofiles/csprofiles.go

@@ -2,8 +2,6 @@ package csprofiles
 
 import (
 	"fmt"
-	"net"
-	"strings"
 
 	"github.com/antonmedv/expr"
 	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
@@ -45,37 +43,6 @@ func GenerateDecisionFromProfile(Profile *csconfig.ProfileCfg, Alert *models.Ale
 		/*for the others, let's populate it from the alert and its source*/
 		decision.Value = new(string)
 		*decision.Value = *Alert.Source.Value
-
-		if strings.EqualFold(*decision.Scope, types.Ip) {
-			srcAddr := net.ParseIP(Alert.Source.IP)
-			if srcAddr == nil {
-				return nil, fmt.Errorf("can't parse ip %s", Alert.Source.IP)
-			}
-			decision.StartIP = int64(types.IP2Int(srcAddr))
-			decision.EndIP = decision.StartIP
-		} else if strings.EqualFold(*decision.Scope, types.Range) {
-
-			/*here we're asked to ban a full range. let's keep in mind that it's not always possible :
-			- the alert is about an IP, but the geolite enrichment failed
-			- the alert is about an IP, but the geolite enrichment isn't present
-			- the alert is about a range, in this case it should succeed
-			*/
-			if Alert.Source.Range != "" {
-				srcAddr, srcRange, err := net.ParseCIDR(Alert.Source.Range)
-				if err != nil {
-					log.Warningf("Profile [%s] requires IP decision, but can't parse '%s' from '%s'",
-						Profile.Name, *Alert.Source.Value, *Alert.Scenario)
-					continue
-				}
-				decision.StartIP = int64(types.IP2Int(srcAddr))
-				decision.EndIP = int64(types.IP2Int(types.LastAddress(srcRange)))
-				decision.Value = new(string)
-				*decision.Value = Alert.Source.Range
-			} else {
-				log.Warningf("Profile [%s] requires scope decision, but information is missing from %s", Profile.Name, *Alert.Scenario)
-				continue
-			}
-		}
 		decision.Origin = new(string)
 		*decision.Origin = "crowdsec"
 		if refDecision.Origin != nil {

+ 91 - 25
pkg/database/alerts.go

@@ -191,17 +191,30 @@ func (c *Client) CreateAlertBulk(machineId string, alertList []*models.Alert) ([
 		if len(alertItem.Decisions) > 0 {
 			decisionBulk := make([]*ent.DecisionCreate, len(alertItem.Decisions))
 			for i, decisionItem := range alertItem.Decisions {
+				var start_ip, start_sfx, end_ip, end_sfx int64
+				var sz int
 
 				duration, err := time.ParseDuration(*decisionItem.Duration)
 				if err != nil {
 					return []string{}, errors.Wrapf(ParseDurationFail, "decision duration '%v' : %s", decisionItem.Duration, err)
 				}
+
+				/*if the scope is IP or Range, convert the value to integers */
+				if strings.ToLower(*decisionItem.Scope) == "ip" || strings.ToLower(*decisionItem.Scope) == "range" {
+					sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(*decisionItem.Value)
+					if err != nil {
+						return []string{}, errors.Wrapf(ParseDurationFail, "invalid addr/range %s : %s", *decisionItem.Value, err)
+					}
+				}
 				decisionBulk[i] = c.Ent.Decision.Create().
 					SetUntil(ts.Add(duration)).
 					SetScenario(*decisionItem.Scenario).
 					SetType(*decisionItem.Type).
-					SetStartIP(decisionItem.StartIP).
-					SetEndIP(decisionItem.EndIP).
+					SetStartIP(start_ip).
+					SetStartSuffix(start_sfx).
+					SetEndIP(end_ip).
+					SetEndSuffix(end_sfx).
+					SetIPSize(int64(sz)).
 					SetValue(*decisionItem.Value).
 					SetScope(*decisionItem.Scope).
 					SetOrigin(*decisionItem.Origin).
@@ -275,8 +288,12 @@ func (c *Client) CreateAlertBulk(machineId string, alertList []*models.Alert) ([
 
 func BuildAlertRequestFromFilter(alerts *ent.AlertQuery, filter map[string][]string) (*ent.AlertQuery, error) {
 	var err error
-	var startIP, endIP int64
+	var start_ip, start_sfx, end_ip, end_sfx int64
 	var hasActiveDecision bool
+	var ip_sz int
+	var contains bool = true
+	/*if contains is true, return bans that *contains* the given value (value is the inner)
+	  else, return bans that are *contained* by the given value (value is the outer)*/
 
 	/*the simulated filter is a bit different : if it's not present *or* set to false, specifically exclude records with simulated to true */
 	if v, ok := filter["simulated"]; ok {
@@ -288,6 +305,11 @@ func BuildAlertRequestFromFilter(alerts *ent.AlertQuery, filter map[string][]str
 
 	for param, value := range filter {
 		switch param {
+		case "contains":
+			contains, err = strconv.ParseBool(value[0])
+			if err != nil {
+				return nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
+			}
 		case "scope":
 			var scope string = value[0]
 			if strings.ToLower(scope) == "ip" {
@@ -300,19 +322,10 @@ func BuildAlertRequestFromFilter(alerts *ent.AlertQuery, filter map[string][]str
 			alerts = alerts.Where(alert.SourceValueEQ(value[0]))
 		case "scenario":
 			alerts = alerts.Where(alert.ScenarioEQ(value[0]))
-		case "ip":
-			isValidIP := IsIpv4(value[0])
-			if !isValidIP {
-				return nil, errors.Wrapf(InvalidIPOrRange, "unable to parse '%s': %s", value[0], err)
-			}
-			startIP, endIP, err = GetIpsFromIpRange(value[0] + "/32")
-			if err != nil {
-				return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int interval: %s", value[0], err)
-			}
-		case "range":
-			startIP, endIP, err = GetIpsFromIpRange(value[0])
+		case "ip", "range":
+			ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
 			if err != nil {
-				return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int interval: %s", value[0], err)
+				return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
 			}
 		case "since":
 			duration, err := types.ParseDuration(value[0])
@@ -369,21 +382,74 @@ func BuildAlertRequestFromFilter(alerts *ent.AlertQuery, filter map[string][]str
 			return nil, errors.Wrapf(InvalidFilter, "Filter parameter '%s' is unknown (=%s)", param, value[0])
 		}
 	}
-	if startIP != 0 && endIP != 0 {
-		/*the user is checking for a single IP*/
-		if startIP == endIP {
-			//DECISION_START <= IP_Q >= DECISON_END
+
+	if ip_sz == 4 {
+		if contains { /*decision contains {start_ip,end_ip}*/
+			alerts = alerts.Where(alert.And(
+				alert.HasDecisionsWith(decision.StartIPLTE(start_ip)),
+				alert.HasDecisionsWith(decision.EndIPGTE(end_ip)),
+				alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
+			))
+		} else { /*decision is contained within {start_ip,end_ip}*/
+			alerts = alerts.Where(alert.And(
+				alert.HasDecisionsWith(decision.StartIPGTE(start_ip)),
+				alert.HasDecisionsWith(decision.EndIPLTE(end_ip)),
+				alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
+			))
+		}
+	} else if ip_sz == 16 {
+
+		if contains { /*decision contains {start_ip,end_ip}*/
 			alerts = alerts.Where(alert.And(
-				alert.HasDecisionsWith(decision.StartIPLTE(startIP)),
-				alert.HasDecisionsWith(decision.EndIPGTE(endIP)),
+				//matching addr size
+				alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
+				alert.Or(
+					//decision.start_ip < query.start_ip
+					alert.HasDecisionsWith(decision.StartIPLT(start_ip)),
+					alert.And(
+						//decision.start_ip == query.start_ip
+						alert.HasDecisionsWith(decision.StartIPEQ(start_ip)),
+						//decision.start_suffix <= query.start_suffix
+						alert.HasDecisionsWith(decision.StartSuffixLTE(start_sfx)),
+					)),
+				alert.Or(
+					//decision.end_ip > query.end_ip
+					alert.HasDecisionsWith(decision.EndIPGT(end_ip)),
+					alert.And(
+						//decision.end_ip == query.end_ip
+						alert.HasDecisionsWith(decision.EndIPEQ(end_ip)),
+						//decision.end_suffix >= query.end_suffix
+						alert.HasDecisionsWith(decision.EndSuffixGTE(end_sfx)),
+					),
+				),
 			))
-		} else { /*the user is checking for a RANGE */
-			//START_Q >= DECISION_START AND END_Q <= DECISION_END
+		} else { /*decision is contained within {start_ip,end_ip}*/
 			alerts = alerts.Where(alert.And(
-				alert.HasDecisionsWith(decision.StartIPGTE(startIP)),
-				alert.HasDecisionsWith(decision.EndIPLTE(endIP)),
+				//matching addr size
+				alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
+				alert.Or(
+					//decision.start_ip > query.start_ip
+					alert.HasDecisionsWith(decision.StartIPGT(start_ip)),
+					alert.And(
+						//decision.start_ip == query.start_ip
+						alert.HasDecisionsWith(decision.StartIPEQ(start_ip)),
+						//decision.start_suffix >= query.start_suffix
+						alert.HasDecisionsWith(decision.StartSuffixGTE(start_sfx)),
+					)),
+				alert.Or(
+					//decision.end_ip < query.end_ip
+					alert.HasDecisionsWith(decision.EndIPLT(end_ip)),
+					alert.And(
+						//decision.end_ip == query.end_ip
+						alert.HasDecisionsWith(decision.EndIPEQ(end_ip)),
+						//decision.end_suffix <= query.end_suffix
+						alert.HasDecisionsWith(decision.EndSuffixLTE(end_sfx)),
+					),
+				),
 			))
 		}
+	} else if ip_sz != 0 {
+		return nil, errors.Wrapf(InvalidFilter, "Unknown ip size %d", ip_sz)
 	}
 	return alerts, nil
 }

+ 240 - 83
pkg/database/decisions.go

@@ -15,8 +15,14 @@ import (
 )
 
 func BuildDecisionRequestWithFilter(query *ent.DecisionQuery, filter map[string][]string) (*ent.DecisionQuery, error) {
+
+	//func BuildDecisionRequestWithFilter(query *ent.Query, filter map[string][]string) (*ent.DecisionQuery, error) {
 	var err error
-	var startIP, endIP int64
+	var start_ip, start_sfx, end_ip, end_sfx int64
+	var ip_sz int
+	var contains bool = true
+	/*if contains is true, return bans that *contains* the given value (value is the inner)
+	  else, return bans that are *contained* by the given value (value is the outer)*/
 
 	/*the simulated filter is a bit different : if it's not present *or* set to false, specifically exclude records with simulated to true */
 	if v, ok := filter["simulated"]; ok {
@@ -30,6 +36,11 @@ func BuildDecisionRequestWithFilter(query *ent.DecisionQuery, filter map[string]
 
 	for param, value := range filter {
 		switch param {
+		case "contains":
+			contains, err = strconv.ParseBool(value[0])
+			if err != nil {
+				return nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
+			}
 		case "scope":
 			var scope string = value[0]
 			if strings.ToLower(scope) == "ip" {
@@ -42,40 +53,84 @@ func BuildDecisionRequestWithFilter(query *ent.DecisionQuery, filter map[string]
 			query = query.Where(decision.ValueEQ(value[0]))
 		case "type":
 			query = query.Where(decision.TypeEQ(value[0]))
-		case "ip":
-			isValidIP := IsIpv4(value[0])
-			if !isValidIP {
-				return nil, errors.Wrapf(InvalidIPOrRange, "unable to parse '%s': %s", value[0], err)
-			}
-			startIP, endIP, err = GetIpsFromIpRange(value[0] + "/32")
-			if err != nil {
-				return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int interval: %s", value[0], err)
-			}
-		case "range":
-			startIP, endIP, err = GetIpsFromIpRange(value[0])
+		case "ip", "range":
+			ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
 			if err != nil {
-				return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int interval: %s", value[0], err)
+				return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
 			}
 		default:
 			return query, errors.Wrapf(InvalidFilter, "'%s' doesn't exist", param)
 		}
 	}
 
-	if startIP != 0 && endIP != 0 {
-		/*the user is checking for a single IP*/
-		if startIP == endIP {
-			//DECISION_START <= IP_Q >= DECISON_END
+	if ip_sz == 4 {
+
+		if contains { /*decision contains {start_ip,end_ip}*/
 			query = query.Where(decision.And(
-				decision.StartIPLTE(startIP),
-				decision.EndIPGTE(endIP),
+				decision.StartIPLTE(start_ip),
+				decision.EndIPGTE(end_ip),
+				decision.IPSizeEQ(int64(ip_sz)),
 			))
-		} else { /*the user is checking for a RANGE */
-			//START_Q >= DECISION_START AND END_Q <= DECISION_END
+		} else { /*decision is contained within {start_ip,end_ip}*/
 			query = query.Where(decision.And(
-				decision.StartIPGTE(startIP),
-				decision.EndIPLTE(endIP),
+				decision.StartIPGTE(start_ip),
+				decision.EndIPLTE(end_ip),
+				decision.IPSizeEQ(int64(ip_sz)),
 			))
 		}
+	} else if ip_sz == 16 {
+
+		if contains { /*decision contains {start_ip,end_ip}*/
+			query = query.Where(decision.And(
+				//matching addr size
+				decision.IPSizeEQ(int64(ip_sz)),
+				decision.Or(
+					//decision.start_ip < query.start_ip
+					decision.StartIPLT(start_ip),
+					decision.And(
+						//decision.start_ip == query.start_ip
+						decision.StartIPEQ(start_ip),
+						//decision.start_suffix <= query.start_suffix
+						decision.StartSuffixLTE(start_sfx),
+					)),
+				decision.Or(
+					//decision.end_ip > query.end_ip
+					decision.EndIPGT(end_ip),
+					decision.And(
+						//decision.end_ip == query.end_ip
+						decision.EndIPEQ(end_ip),
+						//decision.end_suffix >= query.end_suffix
+						decision.EndSuffixGTE(end_sfx),
+					),
+				),
+			))
+		} else { /*decision is contained {start_ip,end_ip}*/
+			query = query.Where(decision.And(
+				//matching addr size
+				decision.IPSizeEQ(int64(ip_sz)),
+				decision.Or(
+					//decision.start_ip > query.start_ip
+					decision.StartIPGT(start_ip),
+					decision.And(
+						//decision.start_ip == query.start_ip
+						decision.StartIPEQ(start_ip),
+						//decision.start_suffix >= query.start_suffix
+						decision.StartSuffixGTE(start_sfx),
+					)),
+				decision.Or(
+					//decision.end_ip < query.end_ip
+					decision.EndIPLT(end_ip),
+					decision.And(
+						//decision.end_ip == query.end_ip
+						decision.EndIPEQ(end_ip),
+						//decision.end_suffix <= query.end_suffix
+						decision.EndSuffixLTE(end_sfx),
+					),
+				),
+			))
+		}
+	} else if ip_sz != 0 {
+		return nil, errors.Wrapf(InvalidFilter, "Unknown ip size %d", ip_sz)
 	}
 	return query, nil
 }
@@ -158,52 +213,101 @@ func (c *Client) DeleteDecisionById(decisionId int) error {
 
 func (c *Client) DeleteDecisionsWithFilter(filter map[string][]string) (string, error) {
 	var err error
-	var startIP, endIP int64
+	var start_ip, start_sfx, end_ip, end_sfx int64
+	var ip_sz int
+	var contains bool = true
+	/*if contains is true, return bans that *contains* the given value (value is the inner)
+	  else, return bans that are *contained* by the given value (value is the outer) */
 
 	decisions := c.Ent.Decision.Delete()
-
 	for param, value := range filter {
 		switch param {
+		case "contains":
+			contains, err = strconv.ParseBool(value[0])
+			if err != nil {
+				return "0", errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
+			}
 		case "scope":
 			decisions = decisions.Where(decision.ScopeEQ(value[0]))
 		case "value":
 			decisions = decisions.Where(decision.ValueEQ(value[0]))
 		case "type":
 			decisions = decisions.Where(decision.TypeEQ(value[0]))
-		case "ip":
-			isValidIP := IsIpv4(value[0])
-			if !isValidIP {
-				return "0", errors.Wrap(InvalidIPOrRange, fmt.Sprintf("unable to parse '%s': %s", value[0], err))
-			}
-			startIP, endIP, err = GetIpsFromIpRange(value[0] + "/32")
-			if err != nil {
-				return "0", errors.Wrap(InvalidIPOrRange, fmt.Sprintf("unable to convert '%s' to int interval: %s", value[0], err))
-			}
-		case "range":
-			startIP, endIP, err = GetIpsFromIpRange(value[0])
+		case "ip", "range":
+			ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
 			if err != nil {
-				return "0", errors.Wrap(InvalidIPOrRange, fmt.Sprintf("unable to convert '%s' to int interval: %s", value[0], err))
+				return "0", errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
 			}
 		default:
 			return "0", errors.Wrap(InvalidFilter, fmt.Sprintf("'%s' doesn't exist", param))
 		}
-
-		if startIP != 0 && endIP != 0 {
-			/*the user is checking for a single IP*/
-			if startIP == endIP {
-				//DECISION_START <= IP_Q >= DECISON_END
-				decisions = decisions.Where(decision.And(
-					decision.StartIPLTE(startIP),
-					decision.EndIPGTE(endIP),
-				))
-			} else { /*the user is checking for a RANGE */
-				//START_Q >= DECISION_START AND END_Q <= DECISION_END
-				decisions = decisions.Where(decision.And(
-					decision.StartIPGTE(startIP),
-					decision.EndIPLTE(endIP),
-				))
-			}
+	}
+	if ip_sz == 4 {
+		if contains { /*decision contains {start_ip,end_ip}*/
+			decisions = decisions.Where(decision.And(
+				decision.StartIPLTE(start_ip),
+				decision.EndIPGTE(end_ip),
+				decision.IPSizeEQ(int64(ip_sz)),
+			))
+		} else { /*decision is contained within {start_ip,end_ip}*/
+			decisions = decisions.Where(decision.And(
+				decision.StartIPGTE(start_ip),
+				decision.EndIPLTE(end_ip),
+				decision.IPSizeEQ(int64(ip_sz)),
+			))
+		}
+	} else if ip_sz == 16 {
+		if contains { /*decision contains {start_ip,end_ip}*/
+			decisions = decisions.Where(decision.And(
+				//matching addr size
+				decision.IPSizeEQ(int64(ip_sz)),
+				decision.Or(
+					//decision.start_ip < query.start_ip
+					decision.StartIPLT(start_ip),
+					decision.And(
+						//decision.start_ip == query.start_ip
+						decision.StartIPEQ(start_ip),
+						//decision.start_suffix <= query.start_suffix
+						decision.StartSuffixLTE(start_sfx),
+					)),
+				decision.Or(
+					//decision.end_ip > query.end_ip
+					decision.EndIPGT(end_ip),
+					decision.And(
+						//decision.end_ip == query.end_ip
+						decision.EndIPEQ(end_ip),
+						//decision.end_suffix >= query.end_suffix
+						decision.EndSuffixGTE(end_sfx),
+					),
+				),
+			))
+		} else {
+			decisions = decisions.Where(decision.And(
+				//matching addr size
+				decision.IPSizeEQ(int64(ip_sz)),
+				decision.Or(
+					//decision.start_ip > query.start_ip
+					decision.StartIPGT(start_ip),
+					decision.And(
+						//decision.start_ip == query.start_ip
+						decision.StartIPEQ(start_ip),
+						//decision.start_suffix >= query.start_suffix
+						decision.StartSuffixGTE(start_sfx),
+					)),
+				decision.Or(
+					//decision.end_ip < query.end_ip
+					decision.EndIPLT(end_ip),
+					decision.And(
+						//decision.end_ip == query.end_ip
+						decision.EndIPEQ(end_ip),
+						//decision.end_suffix <= query.end_suffix
+						decision.EndSuffixLTE(end_sfx),
+					),
+				),
+			))
 		}
+	} else if ip_sz != 0 {
+		return "0", errors.Wrapf(InvalidFilter, "Unknown ip size %d", ip_sz)
 	}
 
 	nbDeleted, err := decisions.Exec(c.CTX)
@@ -217,51 +321,104 @@ func (c *Client) DeleteDecisionsWithFilter(filter map[string][]string) (string,
 // SoftDeleteDecisionsWithFilter udpate the expiration time to now() for the decisions matching the filter
 func (c *Client) SoftDeleteDecisionsWithFilter(filter map[string][]string) (string, error) {
 	var err error
-	var startIP, endIP int64
-
+	var start_ip, start_sfx, end_ip, end_sfx int64
+	var ip_sz int
+	var contains bool = true
+	/*if contains is true, return bans that *contains* the given value (value is the inner)
+	  else, return bans that are *contained* by the given value (value is the outer)*/
 	decisions := c.Ent.Decision.Update().Where(decision.UntilGT(time.Now()))
 	for param, value := range filter {
 		switch param {
+		case "contains":
+			contains, err = strconv.ParseBool(value[0])
+			if err != nil {
+				return "0", errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
+			}
 		case "scope":
 			decisions = decisions.Where(decision.ScopeEQ(value[0]))
 		case "value":
 			decisions = decisions.Where(decision.ValueEQ(value[0]))
 		case "type":
 			decisions = decisions.Where(decision.TypeEQ(value[0]))
-		case "ip":
-			isValidIP := IsIpv4(value[0])
-			if !isValidIP {
-				return "0", errors.Wrapf(InvalidIPOrRange, "unable to parse '%s': %s", value[0], err)
-			}
-			startIP, endIP, err = GetIpsFromIpRange(value[0] + "/32")
+		case "ip", "range":
+			ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
 			if err != nil {
-				return "0", errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int interval: %s", value[0], err)
-			}
-		case "range":
-			startIP, endIP, err = GetIpsFromIpRange(value[0])
-			if err != nil {
-				return "0", errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int interval: %s", value[0], err)
+				return "0", errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
 			}
 		default:
 			return "0", errors.Wrapf(InvalidFilter, "'%s' doesn't exist", param)
 		}
-
-		if startIP != 0 && endIP != 0 {
-			/*the user is checking for a single IP*/
-			if startIP == endIP {
-				//DECISION_START <= IP_Q >= DECISON_END
-				decisions = decisions.Where(decision.And(
-					decision.StartIPLTE(startIP),
-					decision.EndIPGTE(endIP),
-				))
-			} else { /*the user is checking for a RANGE */
-				//START_Q >= DECISION_START AND END_Q <= DECISION_END
-				decisions = decisions.Where(decision.And(
-					decision.StartIPGTE(startIP),
-					decision.EndIPLTE(endIP),
-				))
-			}
+	}
+	if ip_sz == 4 {
+		if contains {
+			/*Decision contains {start_ip,end_ip}*/
+			decisions = decisions.Where(decision.And(
+				decision.StartIPLTE(start_ip),
+				decision.EndIPGTE(end_ip),
+				decision.IPSizeEQ(int64(ip_sz)),
+			))
+		} else {
+			/*Decision is contained within {start_ip,end_ip}*/
+			decisions = decisions.Where(decision.And(
+				decision.StartIPGTE(start_ip),
+				decision.EndIPLTE(end_ip),
+				decision.IPSizeEQ(int64(ip_sz)),
+			))
+		}
+	} else if ip_sz == 16 {
+		/*decision contains {start_ip,end_ip}*/
+		if contains {
+			decisions = decisions.Where(decision.And(
+				//matching addr size
+				decision.IPSizeEQ(int64(ip_sz)),
+				decision.Or(
+					//decision.start_ip < query.start_ip
+					decision.StartIPLT(start_ip),
+					decision.And(
+						//decision.start_ip == query.start_ip
+						decision.StartIPEQ(start_ip),
+						//decision.start_suffix <= query.start_suffix
+						decision.StartSuffixLTE(start_sfx),
+					)),
+				decision.Or(
+					//decision.end_ip > query.end_ip
+					decision.EndIPGT(end_ip),
+					decision.And(
+						//decision.end_ip == query.end_ip
+						decision.EndIPEQ(end_ip),
+						//decision.end_suffix >= query.end_suffix
+						decision.EndSuffixGTE(end_sfx),
+					),
+				),
+			))
+		} else {
+			/*decision is contained within {start_ip,end_ip}*/
+			decisions = decisions.Where(decision.And(
+				//matching addr size
+				decision.IPSizeEQ(int64(ip_sz)),
+				decision.Or(
+					//decision.start_ip > query.start_ip
+					decision.StartIPGT(start_ip),
+					decision.And(
+						//decision.start_ip == query.start_ip
+						decision.StartIPEQ(start_ip),
+						//decision.start_suffix >= query.start_suffix
+						decision.StartSuffixGTE(start_sfx),
+					)),
+				decision.Or(
+					//decision.end_ip < query.end_ip
+					decision.EndIPLT(end_ip),
+					decision.And(
+						//decision.end_ip == query.end_ip
+						decision.EndIPEQ(end_ip),
+						//decision.end_suffix <= query.end_suffix
+						decision.EndSuffixLTE(end_sfx),
+					),
+				),
+			))
 		}
+	} else if ip_sz != 0 {
+		return "0", errors.Wrapf(InvalidFilter, "Unknown ip size %d", ip_sz)
 	}
 	nbDeleted, err := decisions.SetUntil(time.Now()).Save(c.CTX)
 	if err != nil {

+ 176 - 164
pkg/database/ent/alert.go

@@ -124,204 +124,216 @@ func (e AlertEdges) MetasOrErr() ([]*Meta, error) {
 }
 
 // scanValues returns the types for scanning values from sql.Rows.
-func (*Alert) scanValues() []interface{} {
-	return []interface{}{
-		&sql.NullInt64{},   // id
-		&sql.NullTime{},    // created_at
-		&sql.NullTime{},    // updated_at
-		&sql.NullString{},  // scenario
-		&sql.NullString{},  // bucketId
-		&sql.NullString{},  // message
-		&sql.NullInt64{},   // eventsCount
-		&sql.NullTime{},    // startedAt
-		&sql.NullTime{},    // stoppedAt
-		&sql.NullString{},  // sourceIp
-		&sql.NullString{},  // sourceRange
-		&sql.NullString{},  // sourceAsNumber
-		&sql.NullString{},  // sourceAsName
-		&sql.NullString{},  // sourceCountry
-		&sql.NullFloat64{}, // sourceLatitude
-		&sql.NullFloat64{}, // sourceLongitude
-		&sql.NullString{},  // sourceScope
-		&sql.NullString{},  // sourceValue
-		&sql.NullInt64{},   // capacity
-		&sql.NullString{},  // leakSpeed
-		&sql.NullString{},  // scenarioVersion
-		&sql.NullString{},  // scenarioHash
-		&sql.NullBool{},    // simulated
-	}
-}
-
-// fkValues returns the types for scanning foreign-keys values from sql.Rows.
-func (*Alert) fkValues() []interface{} {
-	return []interface{}{
-		&sql.NullInt64{}, // machine_alerts
+func (*Alert) scanValues(columns []string) ([]interface{}, error) {
+	values := make([]interface{}, len(columns))
+	for i := range columns {
+		switch columns[i] {
+		case alert.FieldSimulated:
+			values[i] = &sql.NullBool{}
+		case alert.FieldSourceLatitude, alert.FieldSourceLongitude:
+			values[i] = &sql.NullFloat64{}
+		case alert.FieldID, alert.FieldEventsCount, alert.FieldCapacity:
+			values[i] = &sql.NullInt64{}
+		case alert.FieldScenario, alert.FieldBucketId, alert.FieldMessage, alert.FieldSourceIp, alert.FieldSourceRange, alert.FieldSourceAsNumber, alert.FieldSourceAsName, alert.FieldSourceCountry, alert.FieldSourceScope, alert.FieldSourceValue, alert.FieldLeakSpeed, alert.FieldScenarioVersion, alert.FieldScenarioHash:
+			values[i] = &sql.NullString{}
+		case alert.FieldCreatedAt, alert.FieldUpdatedAt, alert.FieldStartedAt, alert.FieldStoppedAt:
+			values[i] = &sql.NullTime{}
+		case alert.ForeignKeys[0]: // machine_alerts
+			values[i] = &sql.NullInt64{}
+		default:
+			return nil, fmt.Errorf("unexpected column %q for type Alert", columns[i])
+		}
 	}
+	return values, nil
 }
 
 // assignValues assigns the values that were returned from sql.Rows (after scanning)
 // to the Alert fields.
-func (a *Alert) assignValues(values ...interface{}) error {
-	if m, n := len(values), len(alert.Columns); m < n {
+func (a *Alert) assignValues(columns []string, values []interface{}) error {
+	if m, n := len(values), len(columns); m < n {
 		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
 	}
-	value, ok := values[0].(*sql.NullInt64)
-	if !ok {
-		return fmt.Errorf("unexpected type %T for field id", value)
-	}
-	a.ID = int(value.Int64)
-	values = values[1:]
-	if value, ok := values[0].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field created_at", values[0])
-	} else if value.Valid {
-		a.CreatedAt = value.Time
-	}
-	if value, ok := values[1].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field updated_at", values[1])
-	} else if value.Valid {
-		a.UpdatedAt = value.Time
-	}
-	if value, ok := values[2].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field scenario", values[2])
-	} else if value.Valid {
-		a.Scenario = value.String
-	}
-	if value, ok := values[3].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field bucketId", values[3])
-	} else if value.Valid {
-		a.BucketId = value.String
-	}
-	if value, ok := values[4].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field message", values[4])
-	} else if value.Valid {
-		a.Message = value.String
-	}
-	if value, ok := values[5].(*sql.NullInt64); !ok {
-		return fmt.Errorf("unexpected type %T for field eventsCount", values[5])
-	} else if value.Valid {
-		a.EventsCount = int32(value.Int64)
-	}
-	if value, ok := values[6].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field startedAt", values[6])
-	} else if value.Valid {
-		a.StartedAt = value.Time
-	}
-	if value, ok := values[7].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field stoppedAt", values[7])
-	} else if value.Valid {
-		a.StoppedAt = value.Time
-	}
-	if value, ok := values[8].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field sourceIp", values[8])
-	} else if value.Valid {
-		a.SourceIp = value.String
-	}
-	if value, ok := values[9].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field sourceRange", values[9])
-	} else if value.Valid {
-		a.SourceRange = value.String
-	}
-	if value, ok := values[10].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field sourceAsNumber", values[10])
-	} else if value.Valid {
-		a.SourceAsNumber = value.String
-	}
-	if value, ok := values[11].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field sourceAsName", values[11])
-	} else if value.Valid {
-		a.SourceAsName = value.String
-	}
-	if value, ok := values[12].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field sourceCountry", values[12])
-	} else if value.Valid {
-		a.SourceCountry = value.String
-	}
-	if value, ok := values[13].(*sql.NullFloat64); !ok {
-		return fmt.Errorf("unexpected type %T for field sourceLatitude", values[13])
-	} else if value.Valid {
-		a.SourceLatitude = float32(value.Float64)
-	}
-	if value, ok := values[14].(*sql.NullFloat64); !ok {
-		return fmt.Errorf("unexpected type %T for field sourceLongitude", values[14])
-	} else if value.Valid {
-		a.SourceLongitude = float32(value.Float64)
-	}
-	if value, ok := values[15].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field sourceScope", values[15])
-	} else if value.Valid {
-		a.SourceScope = value.String
-	}
-	if value, ok := values[16].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field sourceValue", values[16])
-	} else if value.Valid {
-		a.SourceValue = value.String
-	}
-	if value, ok := values[17].(*sql.NullInt64); !ok {
-		return fmt.Errorf("unexpected type %T for field capacity", values[17])
-	} else if value.Valid {
-		a.Capacity = int32(value.Int64)
-	}
-	if value, ok := values[18].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field leakSpeed", values[18])
-	} else if value.Valid {
-		a.LeakSpeed = value.String
-	}
-	if value, ok := values[19].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field scenarioVersion", values[19])
-	} else if value.Valid {
-		a.ScenarioVersion = value.String
-	}
-	if value, ok := values[20].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field scenarioHash", values[20])
-	} else if value.Valid {
-		a.ScenarioHash = value.String
-	}
-	if value, ok := values[21].(*sql.NullBool); !ok {
-		return fmt.Errorf("unexpected type %T for field simulated", values[21])
-	} else if value.Valid {
-		a.Simulated = value.Bool
-	}
-	values = values[22:]
-	if len(values) == len(alert.ForeignKeys) {
-		if value, ok := values[0].(*sql.NullInt64); !ok {
-			return fmt.Errorf("unexpected type %T for edge-field machine_alerts", value)
-		} else if value.Valid {
-			a.machine_alerts = new(int)
-			*a.machine_alerts = int(value.Int64)
+	for i := range columns {
+		switch columns[i] {
+		case alert.FieldID:
+			value, ok := values[i].(*sql.NullInt64)
+			if !ok {
+				return fmt.Errorf("unexpected type %T for field id", value)
+			}
+			a.ID = int(value.Int64)
+		case alert.FieldCreatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field created_at", values[i])
+			} else if value.Valid {
+				a.CreatedAt = value.Time
+			}
+		case alert.FieldUpdatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field updated_at", values[i])
+			} else if value.Valid {
+				a.UpdatedAt = value.Time
+			}
+		case alert.FieldScenario:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field scenario", values[i])
+			} else if value.Valid {
+				a.Scenario = value.String
+			}
+		case alert.FieldBucketId:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field bucketId", values[i])
+			} else if value.Valid {
+				a.BucketId = value.String
+			}
+		case alert.FieldMessage:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field message", values[i])
+			} else if value.Valid {
+				a.Message = value.String
+			}
+		case alert.FieldEventsCount:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field eventsCount", values[i])
+			} else if value.Valid {
+				a.EventsCount = int32(value.Int64)
+			}
+		case alert.FieldStartedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field startedAt", values[i])
+			} else if value.Valid {
+				a.StartedAt = value.Time
+			}
+		case alert.FieldStoppedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field stoppedAt", values[i])
+			} else if value.Valid {
+				a.StoppedAt = value.Time
+			}
+		case alert.FieldSourceIp:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field sourceIp", values[i])
+			} else if value.Valid {
+				a.SourceIp = value.String
+			}
+		case alert.FieldSourceRange:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field sourceRange", values[i])
+			} else if value.Valid {
+				a.SourceRange = value.String
+			}
+		case alert.FieldSourceAsNumber:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field sourceAsNumber", values[i])
+			} else if value.Valid {
+				a.SourceAsNumber = value.String
+			}
+		case alert.FieldSourceAsName:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field sourceAsName", values[i])
+			} else if value.Valid {
+				a.SourceAsName = value.String
+			}
+		case alert.FieldSourceCountry:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field sourceCountry", values[i])
+			} else if value.Valid {
+				a.SourceCountry = value.String
+			}
+		case alert.FieldSourceLatitude:
+			if value, ok := values[i].(*sql.NullFloat64); !ok {
+				return fmt.Errorf("unexpected type %T for field sourceLatitude", values[i])
+			} else if value.Valid {
+				a.SourceLatitude = float32(value.Float64)
+			}
+		case alert.FieldSourceLongitude:
+			if value, ok := values[i].(*sql.NullFloat64); !ok {
+				return fmt.Errorf("unexpected type %T for field sourceLongitude", values[i])
+			} else if value.Valid {
+				a.SourceLongitude = float32(value.Float64)
+			}
+		case alert.FieldSourceScope:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field sourceScope", values[i])
+			} else if value.Valid {
+				a.SourceScope = value.String
+			}
+		case alert.FieldSourceValue:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field sourceValue", values[i])
+			} else if value.Valid {
+				a.SourceValue = value.String
+			}
+		case alert.FieldCapacity:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field capacity", values[i])
+			} else if value.Valid {
+				a.Capacity = int32(value.Int64)
+			}
+		case alert.FieldLeakSpeed:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field leakSpeed", values[i])
+			} else if value.Valid {
+				a.LeakSpeed = value.String
+			}
+		case alert.FieldScenarioVersion:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field scenarioVersion", values[i])
+			} else if value.Valid {
+				a.ScenarioVersion = value.String
+			}
+		case alert.FieldScenarioHash:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field scenarioHash", values[i])
+			} else if value.Valid {
+				a.ScenarioHash = value.String
+			}
+		case alert.FieldSimulated:
+			if value, ok := values[i].(*sql.NullBool); !ok {
+				return fmt.Errorf("unexpected type %T for field simulated", values[i])
+			} else if value.Valid {
+				a.Simulated = value.Bool
+			}
+		case alert.ForeignKeys[0]:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for edge-field machine_alerts", value)
+			} else if value.Valid {
+				a.machine_alerts = new(int)
+				*a.machine_alerts = int(value.Int64)
+			}
 		}
 	}
 	return nil
 }
 
-// QueryOwner queries the owner edge of the Alert.
+// QueryOwner queries the "owner" edge of the Alert entity.
 func (a *Alert) QueryOwner() *MachineQuery {
 	return (&AlertClient{config: a.config}).QueryOwner(a)
 }
 
-// QueryDecisions queries the decisions edge of the Alert.
+// QueryDecisions queries the "decisions" edge of the Alert entity.
 func (a *Alert) QueryDecisions() *DecisionQuery {
 	return (&AlertClient{config: a.config}).QueryDecisions(a)
 }
 
-// QueryEvents queries the events edge of the Alert.
+// QueryEvents queries the "events" edge of the Alert entity.
 func (a *Alert) QueryEvents() *EventQuery {
 	return (&AlertClient{config: a.config}).QueryEvents(a)
 }
 
-// QueryMetas queries the metas edge of the Alert.
+// QueryMetas queries the "metas" edge of the Alert entity.
 func (a *Alert) QueryMetas() *MetaQuery {
 	return (&AlertClient{config: a.config}).QueryMetas(a)
 }
 
 // Update returns a builder for updating this Alert.
-// Note that, you need to call Alert.Unwrap() before calling this method, if this Alert
+// Note that you need to call Alert.Unwrap() before calling this method if this Alert
 // was returned from a transaction, and the transaction was committed or rolled back.
 func (a *Alert) Update() *AlertUpdateOne {
 	return (&AlertClient{config: a.config}).UpdateOne(a)
 }
 
-// Unwrap unwraps the entity that was returned from a transaction after it was closed,
-// so that all next queries will be executed through the driver which created the transaction.
+// Unwrap unwraps the Alert entity that was returned from a transaction after it was closed,
+// so that all future queries will be executed through the driver which created the transaction.
 func (a *Alert) Unwrap() *Alert {
 	tx, ok := a.config.driver.(*txDriver)
 	if !ok {

+ 8 - 8
pkg/database/ent/alert/alert.go

@@ -145,20 +145,20 @@ func ValidColumn(column string) bool {
 }
 
 var (
-	// DefaultCreatedAt holds the default value on creation for the created_at field.
+	// DefaultCreatedAt holds the default value on creation for the "created_at" field.
 	DefaultCreatedAt func() time.Time
-	// DefaultUpdatedAt holds the default value on creation for the updated_at field.
+	// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
 	DefaultUpdatedAt func() time.Time
-	// DefaultBucketId holds the default value on creation for the bucketId field.
+	// DefaultBucketId holds the default value on creation for the "bucketId" field.
 	DefaultBucketId string
-	// DefaultMessage holds the default value on creation for the message field.
+	// DefaultMessage holds the default value on creation for the "message" field.
 	DefaultMessage string
-	// DefaultEventsCount holds the default value on creation for the eventsCount field.
+	// DefaultEventsCount holds the default value on creation for the "eventsCount" field.
 	DefaultEventsCount int32
-	// DefaultStartedAt holds the default value on creation for the startedAt field.
+	// DefaultStartedAt holds the default value on creation for the "startedAt" field.
 	DefaultStartedAt func() time.Time
-	// DefaultStoppedAt holds the default value on creation for the stoppedAt field.
+	// DefaultStoppedAt holds the default value on creation for the "stoppedAt" field.
 	DefaultStoppedAt func() time.Time
-	// DefaultSimulated holds the default value on creation for the simulated field.
+	// DefaultSimulated holds the default value on creation for the "simulated" field.
 	DefaultSimulated bool
 )

+ 3 - 3
pkg/database/ent/alert/where.go

@@ -10,7 +10,7 @@ import (
 	"github.com/facebook/ent/dialect/sql/sqlgraph"
 )
 
-// ID filters vertices based on their identifier.
+// ID filters vertices based on their ID field.
 func ID(id int) predicate.Alert {
 	return predicate.Alert(func(s *sql.Selector) {
 		s.Where(sql.EQ(s.C(FieldID), id))
@@ -2676,7 +2676,7 @@ func HasMetasWith(preds ...predicate.Meta) predicate.Alert {
 	})
 }
 
-// And groups list of predicates with the AND operator between them.
+// And groups predicates with the AND operator between them.
 func And(predicates ...predicate.Alert) predicate.Alert {
 	return predicate.Alert(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)
@@ -2687,7 +2687,7 @@ func And(predicates ...predicate.Alert) predicate.Alert {
 	})
 }
 
-// Or groups list of predicates with the OR operator between them.
+// Or groups predicates with the OR operator between them.
 func Or(predicates ...predicate.Alert) predicate.Alert {
 	return predicate.Alert(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)

+ 54 - 54
pkg/database/ent/alert_create.go

@@ -24,13 +24,13 @@ type AlertCreate struct {
 	hooks    []Hook
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (ac *AlertCreate) SetCreatedAt(t time.Time) *AlertCreate {
 	ac.mutation.SetCreatedAt(t)
 	return ac
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableCreatedAt(t *time.Time) *AlertCreate {
 	if t != nil {
 		ac.SetCreatedAt(*t)
@@ -38,13 +38,13 @@ func (ac *AlertCreate) SetNillableCreatedAt(t *time.Time) *AlertCreate {
 	return ac
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (ac *AlertCreate) SetUpdatedAt(t time.Time) *AlertCreate {
 	ac.mutation.SetUpdatedAt(t)
 	return ac
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableUpdatedAt(t *time.Time) *AlertCreate {
 	if t != nil {
 		ac.SetUpdatedAt(*t)
@@ -52,19 +52,19 @@ func (ac *AlertCreate) SetNillableUpdatedAt(t *time.Time) *AlertCreate {
 	return ac
 }
 
-// SetScenario sets the scenario field.
+// SetScenario sets the "scenario" field.
 func (ac *AlertCreate) SetScenario(s string) *AlertCreate {
 	ac.mutation.SetScenario(s)
 	return ac
 }
 
-// SetBucketId sets the bucketId field.
+// SetBucketId sets the "bucketId" field.
 func (ac *AlertCreate) SetBucketId(s string) *AlertCreate {
 	ac.mutation.SetBucketId(s)
 	return ac
 }
 
-// SetNillableBucketId sets the bucketId field if the given value is not nil.
+// SetNillableBucketId sets the "bucketId" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableBucketId(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetBucketId(*s)
@@ -72,13 +72,13 @@ func (ac *AlertCreate) SetNillableBucketId(s *string) *AlertCreate {
 	return ac
 }
 
-// SetMessage sets the message field.
+// SetMessage sets the "message" field.
 func (ac *AlertCreate) SetMessage(s string) *AlertCreate {
 	ac.mutation.SetMessage(s)
 	return ac
 }
 
-// SetNillableMessage sets the message field if the given value is not nil.
+// SetNillableMessage sets the "message" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableMessage(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetMessage(*s)
@@ -86,13 +86,13 @@ func (ac *AlertCreate) SetNillableMessage(s *string) *AlertCreate {
 	return ac
 }
 
-// SetEventsCount sets the eventsCount field.
+// SetEventsCount sets the "eventsCount" field.
 func (ac *AlertCreate) SetEventsCount(i int32) *AlertCreate {
 	ac.mutation.SetEventsCount(i)
 	return ac
 }
 
-// SetNillableEventsCount sets the eventsCount field if the given value is not nil.
+// SetNillableEventsCount sets the "eventsCount" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableEventsCount(i *int32) *AlertCreate {
 	if i != nil {
 		ac.SetEventsCount(*i)
@@ -100,13 +100,13 @@ func (ac *AlertCreate) SetNillableEventsCount(i *int32) *AlertCreate {
 	return ac
 }
 
-// SetStartedAt sets the startedAt field.
+// SetStartedAt sets the "startedAt" field.
 func (ac *AlertCreate) SetStartedAt(t time.Time) *AlertCreate {
 	ac.mutation.SetStartedAt(t)
 	return ac
 }
 
-// SetNillableStartedAt sets the startedAt field if the given value is not nil.
+// SetNillableStartedAt sets the "startedAt" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableStartedAt(t *time.Time) *AlertCreate {
 	if t != nil {
 		ac.SetStartedAt(*t)
@@ -114,13 +114,13 @@ func (ac *AlertCreate) SetNillableStartedAt(t *time.Time) *AlertCreate {
 	return ac
 }
 
-// SetStoppedAt sets the stoppedAt field.
+// SetStoppedAt sets the "stoppedAt" field.
 func (ac *AlertCreate) SetStoppedAt(t time.Time) *AlertCreate {
 	ac.mutation.SetStoppedAt(t)
 	return ac
 }
 
-// SetNillableStoppedAt sets the stoppedAt field if the given value is not nil.
+// SetNillableStoppedAt sets the "stoppedAt" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableStoppedAt(t *time.Time) *AlertCreate {
 	if t != nil {
 		ac.SetStoppedAt(*t)
@@ -128,13 +128,13 @@ func (ac *AlertCreate) SetNillableStoppedAt(t *time.Time) *AlertCreate {
 	return ac
 }
 
-// SetSourceIp sets the sourceIp field.
+// SetSourceIp sets the "sourceIp" field.
 func (ac *AlertCreate) SetSourceIp(s string) *AlertCreate {
 	ac.mutation.SetSourceIp(s)
 	return ac
 }
 
-// SetNillableSourceIp sets the sourceIp field if the given value is not nil.
+// SetNillableSourceIp sets the "sourceIp" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableSourceIp(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetSourceIp(*s)
@@ -142,13 +142,13 @@ func (ac *AlertCreate) SetNillableSourceIp(s *string) *AlertCreate {
 	return ac
 }
 
-// SetSourceRange sets the sourceRange field.
+// SetSourceRange sets the "sourceRange" field.
 func (ac *AlertCreate) SetSourceRange(s string) *AlertCreate {
 	ac.mutation.SetSourceRange(s)
 	return ac
 }
 
-// SetNillableSourceRange sets the sourceRange field if the given value is not nil.
+// SetNillableSourceRange sets the "sourceRange" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableSourceRange(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetSourceRange(*s)
@@ -156,13 +156,13 @@ func (ac *AlertCreate) SetNillableSourceRange(s *string) *AlertCreate {
 	return ac
 }
 
-// SetSourceAsNumber sets the sourceAsNumber field.
+// SetSourceAsNumber sets the "sourceAsNumber" field.
 func (ac *AlertCreate) SetSourceAsNumber(s string) *AlertCreate {
 	ac.mutation.SetSourceAsNumber(s)
 	return ac
 }
 
-// SetNillableSourceAsNumber sets the sourceAsNumber field if the given value is not nil.
+// SetNillableSourceAsNumber sets the "sourceAsNumber" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableSourceAsNumber(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetSourceAsNumber(*s)
@@ -170,13 +170,13 @@ func (ac *AlertCreate) SetNillableSourceAsNumber(s *string) *AlertCreate {
 	return ac
 }
 
-// SetSourceAsName sets the sourceAsName field.
+// SetSourceAsName sets the "sourceAsName" field.
 func (ac *AlertCreate) SetSourceAsName(s string) *AlertCreate {
 	ac.mutation.SetSourceAsName(s)
 	return ac
 }
 
-// SetNillableSourceAsName sets the sourceAsName field if the given value is not nil.
+// SetNillableSourceAsName sets the "sourceAsName" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableSourceAsName(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetSourceAsName(*s)
@@ -184,13 +184,13 @@ func (ac *AlertCreate) SetNillableSourceAsName(s *string) *AlertCreate {
 	return ac
 }
 
-// SetSourceCountry sets the sourceCountry field.
+// SetSourceCountry sets the "sourceCountry" field.
 func (ac *AlertCreate) SetSourceCountry(s string) *AlertCreate {
 	ac.mutation.SetSourceCountry(s)
 	return ac
 }
 
-// SetNillableSourceCountry sets the sourceCountry field if the given value is not nil.
+// SetNillableSourceCountry sets the "sourceCountry" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableSourceCountry(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetSourceCountry(*s)
@@ -198,13 +198,13 @@ func (ac *AlertCreate) SetNillableSourceCountry(s *string) *AlertCreate {
 	return ac
 }
 
-// SetSourceLatitude sets the sourceLatitude field.
+// SetSourceLatitude sets the "sourceLatitude" field.
 func (ac *AlertCreate) SetSourceLatitude(f float32) *AlertCreate {
 	ac.mutation.SetSourceLatitude(f)
 	return ac
 }
 
-// SetNillableSourceLatitude sets the sourceLatitude field if the given value is not nil.
+// SetNillableSourceLatitude sets the "sourceLatitude" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableSourceLatitude(f *float32) *AlertCreate {
 	if f != nil {
 		ac.SetSourceLatitude(*f)
@@ -212,13 +212,13 @@ func (ac *AlertCreate) SetNillableSourceLatitude(f *float32) *AlertCreate {
 	return ac
 }
 
-// SetSourceLongitude sets the sourceLongitude field.
+// SetSourceLongitude sets the "sourceLongitude" field.
 func (ac *AlertCreate) SetSourceLongitude(f float32) *AlertCreate {
 	ac.mutation.SetSourceLongitude(f)
 	return ac
 }
 
-// SetNillableSourceLongitude sets the sourceLongitude field if the given value is not nil.
+// SetNillableSourceLongitude sets the "sourceLongitude" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableSourceLongitude(f *float32) *AlertCreate {
 	if f != nil {
 		ac.SetSourceLongitude(*f)
@@ -226,13 +226,13 @@ func (ac *AlertCreate) SetNillableSourceLongitude(f *float32) *AlertCreate {
 	return ac
 }
 
-// SetSourceScope sets the sourceScope field.
+// SetSourceScope sets the "sourceScope" field.
 func (ac *AlertCreate) SetSourceScope(s string) *AlertCreate {
 	ac.mutation.SetSourceScope(s)
 	return ac
 }
 
-// SetNillableSourceScope sets the sourceScope field if the given value is not nil.
+// SetNillableSourceScope sets the "sourceScope" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableSourceScope(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetSourceScope(*s)
@@ -240,13 +240,13 @@ func (ac *AlertCreate) SetNillableSourceScope(s *string) *AlertCreate {
 	return ac
 }
 
-// SetSourceValue sets the sourceValue field.
+// SetSourceValue sets the "sourceValue" field.
 func (ac *AlertCreate) SetSourceValue(s string) *AlertCreate {
 	ac.mutation.SetSourceValue(s)
 	return ac
 }
 
-// SetNillableSourceValue sets the sourceValue field if the given value is not nil.
+// SetNillableSourceValue sets the "sourceValue" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableSourceValue(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetSourceValue(*s)
@@ -254,13 +254,13 @@ func (ac *AlertCreate) SetNillableSourceValue(s *string) *AlertCreate {
 	return ac
 }
 
-// SetCapacity sets the capacity field.
+// SetCapacity sets the "capacity" field.
 func (ac *AlertCreate) SetCapacity(i int32) *AlertCreate {
 	ac.mutation.SetCapacity(i)
 	return ac
 }
 
-// SetNillableCapacity sets the capacity field if the given value is not nil.
+// SetNillableCapacity sets the "capacity" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableCapacity(i *int32) *AlertCreate {
 	if i != nil {
 		ac.SetCapacity(*i)
@@ -268,13 +268,13 @@ func (ac *AlertCreate) SetNillableCapacity(i *int32) *AlertCreate {
 	return ac
 }
 
-// SetLeakSpeed sets the leakSpeed field.
+// SetLeakSpeed sets the "leakSpeed" field.
 func (ac *AlertCreate) SetLeakSpeed(s string) *AlertCreate {
 	ac.mutation.SetLeakSpeed(s)
 	return ac
 }
 
-// SetNillableLeakSpeed sets the leakSpeed field if the given value is not nil.
+// SetNillableLeakSpeed sets the "leakSpeed" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableLeakSpeed(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetLeakSpeed(*s)
@@ -282,13 +282,13 @@ func (ac *AlertCreate) SetNillableLeakSpeed(s *string) *AlertCreate {
 	return ac
 }
 
-// SetScenarioVersion sets the scenarioVersion field.
+// SetScenarioVersion sets the "scenarioVersion" field.
 func (ac *AlertCreate) SetScenarioVersion(s string) *AlertCreate {
 	ac.mutation.SetScenarioVersion(s)
 	return ac
 }
 
-// SetNillableScenarioVersion sets the scenarioVersion field if the given value is not nil.
+// SetNillableScenarioVersion sets the "scenarioVersion" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableScenarioVersion(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetScenarioVersion(*s)
@@ -296,13 +296,13 @@ func (ac *AlertCreate) SetNillableScenarioVersion(s *string) *AlertCreate {
 	return ac
 }
 
-// SetScenarioHash sets the scenarioHash field.
+// SetScenarioHash sets the "scenarioHash" field.
 func (ac *AlertCreate) SetScenarioHash(s string) *AlertCreate {
 	ac.mutation.SetScenarioHash(s)
 	return ac
 }
 
-// SetNillableScenarioHash sets the scenarioHash field if the given value is not nil.
+// SetNillableScenarioHash sets the "scenarioHash" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableScenarioHash(s *string) *AlertCreate {
 	if s != nil {
 		ac.SetScenarioHash(*s)
@@ -310,13 +310,13 @@ func (ac *AlertCreate) SetNillableScenarioHash(s *string) *AlertCreate {
 	return ac
 }
 
-// SetSimulated sets the simulated field.
+// SetSimulated sets the "simulated" field.
 func (ac *AlertCreate) SetSimulated(b bool) *AlertCreate {
 	ac.mutation.SetSimulated(b)
 	return ac
 }
 
-// SetNillableSimulated sets the simulated field if the given value is not nil.
+// SetNillableSimulated sets the "simulated" field if the given value is not nil.
 func (ac *AlertCreate) SetNillableSimulated(b *bool) *AlertCreate {
 	if b != nil {
 		ac.SetSimulated(*b)
@@ -324,13 +324,13 @@ func (ac *AlertCreate) SetNillableSimulated(b *bool) *AlertCreate {
 	return ac
 }
 
-// SetOwnerID sets the owner edge to Machine by id.
+// SetOwnerID sets the "owner" edge to the Machine entity by ID.
 func (ac *AlertCreate) SetOwnerID(id int) *AlertCreate {
 	ac.mutation.SetOwnerID(id)
 	return ac
 }
 
-// SetNillableOwnerID sets the owner edge to Machine by id if the given value is not nil.
+// SetNillableOwnerID sets the "owner" edge to the Machine entity by ID if the given value is not nil.
 func (ac *AlertCreate) SetNillableOwnerID(id *int) *AlertCreate {
 	if id != nil {
 		ac = ac.SetOwnerID(*id)
@@ -338,18 +338,18 @@ func (ac *AlertCreate) SetNillableOwnerID(id *int) *AlertCreate {
 	return ac
 }
 
-// SetOwner sets the owner edge to Machine.
+// SetOwner sets the "owner" edge to the Machine entity.
 func (ac *AlertCreate) SetOwner(m *Machine) *AlertCreate {
 	return ac.SetOwnerID(m.ID)
 }
 
-// AddDecisionIDs adds the decisions edge to Decision by ids.
+// AddDecisionIDs adds the "decisions" edge to the Decision entity by IDs.
 func (ac *AlertCreate) AddDecisionIDs(ids ...int) *AlertCreate {
 	ac.mutation.AddDecisionIDs(ids...)
 	return ac
 }
 
-// AddDecisions adds the decisions edges to Decision.
+// AddDecisions adds the "decisions" edges to the Decision entity.
 func (ac *AlertCreate) AddDecisions(d ...*Decision) *AlertCreate {
 	ids := make([]int, len(d))
 	for i := range d {
@@ -358,13 +358,13 @@ func (ac *AlertCreate) AddDecisions(d ...*Decision) *AlertCreate {
 	return ac.AddDecisionIDs(ids...)
 }
 
-// AddEventIDs adds the events edge to Event by ids.
+// AddEventIDs adds the "events" edge to the Event entity by IDs.
 func (ac *AlertCreate) AddEventIDs(ids ...int) *AlertCreate {
 	ac.mutation.AddEventIDs(ids...)
 	return ac
 }
 
-// AddEvents adds the events edges to Event.
+// AddEvents adds the "events" edges to the Event entity.
 func (ac *AlertCreate) AddEvents(e ...*Event) *AlertCreate {
 	ids := make([]int, len(e))
 	for i := range e {
@@ -373,13 +373,13 @@ func (ac *AlertCreate) AddEvents(e ...*Event) *AlertCreate {
 	return ac.AddEventIDs(ids...)
 }
 
-// AddMetaIDs adds the metas edge to Meta by ids.
+// AddMetaIDs adds the "metas" edge to the Meta entity by IDs.
 func (ac *AlertCreate) AddMetaIDs(ids ...int) *AlertCreate {
 	ac.mutation.AddMetaIDs(ids...)
 	return ac
 }
 
-// AddMetas adds the metas edges to Meta.
+// AddMetas adds the "metas" edges to the Meta entity.
 func (ac *AlertCreate) AddMetas(m ...*Meta) *AlertCreate {
 	ids := make([]int, len(m))
 	for i := range m {
@@ -770,7 +770,7 @@ func (ac *AlertCreate) createSpec() (*Alert, *sqlgraph.CreateSpec) {
 	return _node, _spec
 }
 
-// AlertCreateBulk is the builder for creating a bulk of Alert entities.
+// AlertCreateBulk is the builder for creating many Alert entities in bulk.
 type AlertCreateBulk struct {
 	config
 	builders []*AlertCreate
@@ -828,7 +828,7 @@ func (acb *AlertCreateBulk) Save(ctx context.Context) ([]*Alert, error) {
 	return nodes, nil
 }
 
-// SaveX calls Save and panics if Save returns an error.
+// SaveX is like Save, but panics if an error occurs.
 func (acb *AlertCreateBulk) SaveX(ctx context.Context) []*Alert {
 	v, err := acb.Save(ctx)
 	if err != nil {

+ 5 - 6
pkg/database/ent/alert_delete.go

@@ -16,14 +16,13 @@ import (
 // AlertDelete is the builder for deleting a Alert entity.
 type AlertDelete struct {
 	config
-	hooks      []Hook
-	mutation   *AlertMutation
-	predicates []predicate.Alert
+	hooks    []Hook
+	mutation *AlertMutation
 }
 
-// Where adds a new predicate to the delete builder.
+// Where adds a new predicate to the AlertDelete builder.
 func (ad *AlertDelete) Where(ps ...predicate.Alert) *AlertDelete {
-	ad.predicates = append(ad.predicates, ps...)
+	ad.mutation.predicates = append(ad.mutation.predicates, ps...)
 	return ad
 }
 
@@ -75,7 +74,7 @@ func (ad *AlertDelete) sqlExec(ctx context.Context) (int, error) {
 			},
 		},
 	}
-	if ps := ad.predicates; len(ps) > 0 {
+	if ps := ad.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)

+ 98 - 79
pkg/database/ent/alert_query.go

@@ -26,7 +26,7 @@ type AlertQuery struct {
 	limit      *int
 	offset     *int
 	order      []OrderFunc
-	unique     []string
+	fields     []string
 	predicates []predicate.Alert
 	// eager-loading edges.
 	withOwner     *MachineQuery
@@ -39,7 +39,7 @@ type AlertQuery struct {
 	path func(context.Context) (*sql.Selector, error)
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the AlertQuery builder.
 func (aq *AlertQuery) Where(ps ...predicate.Alert) *AlertQuery {
 	aq.predicates = append(aq.predicates, ps...)
 	return aq
@@ -63,7 +63,7 @@ func (aq *AlertQuery) Order(o ...OrderFunc) *AlertQuery {
 	return aq
 }
 
-// QueryOwner chains the current query on the owner edge.
+// QueryOwner chains the current query on the "owner" edge.
 func (aq *AlertQuery) QueryOwner() *MachineQuery {
 	query := &MachineQuery{config: aq.config}
 	query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
@@ -85,7 +85,7 @@ func (aq *AlertQuery) QueryOwner() *MachineQuery {
 	return query
 }
 
-// QueryDecisions chains the current query on the decisions edge.
+// QueryDecisions chains the current query on the "decisions" edge.
 func (aq *AlertQuery) QueryDecisions() *DecisionQuery {
 	query := &DecisionQuery{config: aq.config}
 	query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
@@ -107,7 +107,7 @@ func (aq *AlertQuery) QueryDecisions() *DecisionQuery {
 	return query
 }
 
-// QueryEvents chains the current query on the events edge.
+// QueryEvents chains the current query on the "events" edge.
 func (aq *AlertQuery) QueryEvents() *EventQuery {
 	query := &EventQuery{config: aq.config}
 	query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
@@ -129,7 +129,7 @@ func (aq *AlertQuery) QueryEvents() *EventQuery {
 	return query
 }
 
-// QueryMetas chains the current query on the metas edge.
+// QueryMetas chains the current query on the "metas" edge.
 func (aq *AlertQuery) QueryMetas() *MetaQuery {
 	query := &MetaQuery{config: aq.config}
 	query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
@@ -151,7 +151,8 @@ func (aq *AlertQuery) QueryMetas() *MetaQuery {
 	return query
 }
 
-// First returns the first Alert entity in the query. Returns *NotFoundError when no alert was found.
+// First returns the first Alert entity from the query.
+// Returns a *NotFoundError when no Alert was found.
 func (aq *AlertQuery) First(ctx context.Context) (*Alert, error) {
 	nodes, err := aq.Limit(1).All(ctx)
 	if err != nil {
@@ -172,7 +173,8 @@ func (aq *AlertQuery) FirstX(ctx context.Context) *Alert {
 	return node
 }
 
-// FirstID returns the first Alert id in the query. Returns *NotFoundError when no id was found.
+// FirstID returns the first Alert ID from the query.
+// Returns a *NotFoundError when no Alert ID was found.
 func (aq *AlertQuery) FirstID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = aq.Limit(1).IDs(ctx); err != nil {
@@ -185,8 +187,8 @@ func (aq *AlertQuery) FirstID(ctx context.Context) (id int, err error) {
 	return ids[0], nil
 }
 
-// FirstXID is like FirstID, but panics if an error occurs.
-func (aq *AlertQuery) FirstXID(ctx context.Context) int {
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (aq *AlertQuery) FirstIDX(ctx context.Context) int {
 	id, err := aq.FirstID(ctx)
 	if err != nil && !IsNotFound(err) {
 		panic(err)
@@ -194,7 +196,9 @@ func (aq *AlertQuery) FirstXID(ctx context.Context) int {
 	return id
 }
 
-// Only returns the only Alert entity in the query, returns an error if not exactly one entity was returned.
+// Only returns a single Alert entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when exactly one Alert entity is not found.
+// Returns a *NotFoundError when no Alert entities are found.
 func (aq *AlertQuery) Only(ctx context.Context) (*Alert, error) {
 	nodes, err := aq.Limit(2).All(ctx)
 	if err != nil {
@@ -219,7 +223,9 @@ func (aq *AlertQuery) OnlyX(ctx context.Context) *Alert {
 	return node
 }
 
-// OnlyID returns the only Alert id in the query, returns an error if not exactly one id was returned.
+// OnlyID is like Only, but returns the only Alert ID in the query.
+// Returns a *NotSingularError when exactly one Alert ID is not found.
+// Returns a *NotFoundError when no entities are found.
 func (aq *AlertQuery) OnlyID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = aq.Limit(2).IDs(ctx); err != nil {
@@ -262,7 +268,7 @@ func (aq *AlertQuery) AllX(ctx context.Context) []*Alert {
 	return nodes
 }
 
-// IDs executes the query and returns a list of Alert ids.
+// IDs executes the query and returns a list of Alert IDs.
 func (aq *AlertQuery) IDs(ctx context.Context) ([]int, error) {
 	var ids []int
 	if err := aq.Select(alert.FieldID).Scan(ctx, &ids); err != nil {
@@ -314,24 +320,30 @@ func (aq *AlertQuery) ExistX(ctx context.Context) bool {
 	return exist
 }
 
-// Clone returns a duplicate of the query builder, including all associated steps. It can be
+// Clone returns a duplicate of the AlertQuery builder, including all associated steps. It can be
 // used to prepare common query builders and use them differently after the clone is made.
 func (aq *AlertQuery) Clone() *AlertQuery {
+	if aq == nil {
+		return nil
+	}
 	return &AlertQuery{
-		config:     aq.config,
-		limit:      aq.limit,
-		offset:     aq.offset,
-		order:      append([]OrderFunc{}, aq.order...),
-		unique:     append([]string{}, aq.unique...),
-		predicates: append([]predicate.Alert{}, aq.predicates...),
+		config:        aq.config,
+		limit:         aq.limit,
+		offset:        aq.offset,
+		order:         append([]OrderFunc{}, aq.order...),
+		predicates:    append([]predicate.Alert{}, aq.predicates...),
+		withOwner:     aq.withOwner.Clone(),
+		withDecisions: aq.withDecisions.Clone(),
+		withEvents:    aq.withEvents.Clone(),
+		withMetas:     aq.withMetas.Clone(),
 		// clone intermediate query.
 		sql:  aq.sql.Clone(),
 		path: aq.path,
 	}
 }
 
-//  WithOwner tells the query-builder to eager-loads the nodes that are connected to
-// the "owner" edge. The optional arguments used to configure the query builder of the edge.
+// WithOwner tells the query-builder to eager-load the nodes that are connected to
+// the "owner" edge. The optional arguments are used to configure the query builder of the edge.
 func (aq *AlertQuery) WithOwner(opts ...func(*MachineQuery)) *AlertQuery {
 	query := &MachineQuery{config: aq.config}
 	for _, opt := range opts {
@@ -341,8 +353,8 @@ func (aq *AlertQuery) WithOwner(opts ...func(*MachineQuery)) *AlertQuery {
 	return aq
 }
 
-//  WithDecisions tells the query-builder to eager-loads the nodes that are connected to
-// the "decisions" edge. The optional arguments used to configure the query builder of the edge.
+// WithDecisions tells the query-builder to eager-load the nodes that are connected to
+// the "decisions" edge. The optional arguments are used to configure the query builder of the edge.
 func (aq *AlertQuery) WithDecisions(opts ...func(*DecisionQuery)) *AlertQuery {
 	query := &DecisionQuery{config: aq.config}
 	for _, opt := range opts {
@@ -352,8 +364,8 @@ func (aq *AlertQuery) WithDecisions(opts ...func(*DecisionQuery)) *AlertQuery {
 	return aq
 }
 
-//  WithEvents tells the query-builder to eager-loads the nodes that are connected to
-// the "events" edge. The optional arguments used to configure the query builder of the edge.
+// WithEvents tells the query-builder to eager-load the nodes that are connected to
+// the "events" edge. The optional arguments are used to configure the query builder of the edge.
 func (aq *AlertQuery) WithEvents(opts ...func(*EventQuery)) *AlertQuery {
 	query := &EventQuery{config: aq.config}
 	for _, opt := range opts {
@@ -363,8 +375,8 @@ func (aq *AlertQuery) WithEvents(opts ...func(*EventQuery)) *AlertQuery {
 	return aq
 }
 
-//  WithMetas tells the query-builder to eager-loads the nodes that are connected to
-// the "metas" edge. The optional arguments used to configure the query builder of the edge.
+// WithMetas tells the query-builder to eager-load the nodes that are connected to
+// the "metas" edge. The optional arguments are used to configure the query builder of the edge.
 func (aq *AlertQuery) WithMetas(opts ...func(*MetaQuery)) *AlertQuery {
 	query := &MetaQuery{config: aq.config}
 	for _, opt := range opts {
@@ -374,7 +386,7 @@ func (aq *AlertQuery) WithMetas(opts ...func(*MetaQuery)) *AlertQuery {
 	return aq
 }
 
-// GroupBy used to group vertices by one or more fields/columns.
+// GroupBy is used to group vertices by one or more fields/columns.
 // It is often used with aggregate functions, like: count, max, mean, min, sum.
 //
 // Example:
@@ -401,7 +413,8 @@ func (aq *AlertQuery) GroupBy(field string, fields ...string) *AlertGroupBy {
 	return group
 }
 
-// Select one or more fields from the given query.
+// Select allows the selection one or more fields/columns for the given query,
+// instead of selecting all fields in the entity.
 //
 // Example:
 //
@@ -414,18 +427,16 @@ func (aq *AlertQuery) GroupBy(field string, fields ...string) *AlertGroupBy {
 //		Scan(ctx, &v)
 //
 func (aq *AlertQuery) Select(field string, fields ...string) *AlertSelect {
-	selector := &AlertSelect{config: aq.config}
-	selector.fields = append([]string{field}, fields...)
-	selector.path = func(ctx context.Context) (prev *sql.Selector, err error) {
-		if err := aq.prepareQuery(ctx); err != nil {
-			return nil, err
-		}
-		return aq.sqlQuery(), nil
-	}
-	return selector
+	aq.fields = append([]string{field}, fields...)
+	return &AlertSelect{AlertQuery: aq}
 }
 
 func (aq *AlertQuery) prepareQuery(ctx context.Context) error {
+	for _, f := range aq.fields {
+		if !alert.ValidColumn(f) {
+			return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+		}
+	}
 	if aq.path != nil {
 		prev, err := aq.path(ctx)
 		if err != nil {
@@ -454,22 +465,18 @@ func (aq *AlertQuery) sqlAll(ctx context.Context) ([]*Alert, error) {
 	if withFKs {
 		_spec.Node.Columns = append(_spec.Node.Columns, alert.ForeignKeys...)
 	}
-	_spec.ScanValues = func() []interface{} {
+	_spec.ScanValues = func(columns []string) ([]interface{}, error) {
 		node := &Alert{config: aq.config}
 		nodes = append(nodes, node)
-		values := node.scanValues()
-		if withFKs {
-			values = append(values, node.fkValues()...)
-		}
-		return values
+		return node.scanValues(columns)
 	}
-	_spec.Assign = func(values ...interface{}) error {
+	_spec.Assign = func(columns []string, values []interface{}) error {
 		if len(nodes) == 0 {
 			return fmt.Errorf("ent: Assign called without calling ScanValues")
 		}
 		node := nodes[len(nodes)-1]
 		node.Edges.loadedTypes = loadedTypes
-		return node.assignValues(values...)
+		return node.assignValues(columns, values)
 	}
 	if err := sqlgraph.QueryNodes(ctx, aq.driver, _spec); err != nil {
 		return nil, err
@@ -509,6 +516,7 @@ func (aq *AlertQuery) sqlAll(ctx context.Context) ([]*Alert, error) {
 		for i := range nodes {
 			fks = append(fks, nodes[i].ID)
 			nodeids[nodes[i].ID] = nodes[i]
+			nodes[i].Edges.Decisions = []*Decision{}
 		}
 		query.withFKs = true
 		query.Where(predicate.Decision(func(s *sql.Selector) {
@@ -537,6 +545,7 @@ func (aq *AlertQuery) sqlAll(ctx context.Context) ([]*Alert, error) {
 		for i := range nodes {
 			fks = append(fks, nodes[i].ID)
 			nodeids[nodes[i].ID] = nodes[i]
+			nodes[i].Edges.Events = []*Event{}
 		}
 		query.withFKs = true
 		query.Where(predicate.Event(func(s *sql.Selector) {
@@ -565,6 +574,7 @@ func (aq *AlertQuery) sqlAll(ctx context.Context) ([]*Alert, error) {
 		for i := range nodes {
 			fks = append(fks, nodes[i].ID)
 			nodeids[nodes[i].ID] = nodes[i]
+			nodes[i].Edges.Metas = []*Meta{}
 		}
 		query.withFKs = true
 		query.Where(predicate.Meta(func(s *sql.Selector) {
@@ -616,6 +626,15 @@ func (aq *AlertQuery) querySpec() *sqlgraph.QuerySpec {
 		From:   aq.sql,
 		Unique: true,
 	}
+	if fields := aq.fields; len(fields) > 0 {
+		_spec.Node.Columns = make([]string, 0, len(fields))
+		_spec.Node.Columns = append(_spec.Node.Columns, alert.FieldID)
+		for i := range fields {
+			if fields[i] != alert.FieldID {
+				_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+			}
+		}
+	}
 	if ps := aq.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
@@ -664,7 +683,7 @@ func (aq *AlertQuery) sqlQuery() *sql.Selector {
 	return selector
 }
 
-// AlertGroupBy is the builder for group-by Alert entities.
+// AlertGroupBy is the group-by builder for Alert entities.
 type AlertGroupBy struct {
 	config
 	fields []string
@@ -680,7 +699,7 @@ func (agb *AlertGroupBy) Aggregate(fns ...AggregateFunc) *AlertGroupBy {
 	return agb
 }
 
-// Scan applies the group-by query and scan the result into the given value.
+// Scan applies the group-by query and scans the result into the given value.
 func (agb *AlertGroupBy) Scan(ctx context.Context, v interface{}) error {
 	query, err := agb.path(ctx)
 	if err != nil {
@@ -697,7 +716,8 @@ func (agb *AlertGroupBy) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from group-by. It is only allowed when querying group-by with one field.
+// Strings returns list of strings from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (agb *AlertGroupBy) Strings(ctx context.Context) ([]string, error) {
 	if len(agb.fields) > 1 {
 		return nil, errors.New("ent: AlertGroupBy.Strings is not achievable when grouping more than 1 field")
@@ -718,7 +738,8 @@ func (agb *AlertGroupBy) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from group-by. It is only allowed when querying group-by with one field.
+// String returns a single string from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (agb *AlertGroupBy) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = agb.Strings(ctx); err != nil {
@@ -744,7 +765,8 @@ func (agb *AlertGroupBy) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from group-by. It is only allowed when querying group-by with one field.
+// Ints returns list of ints from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (agb *AlertGroupBy) Ints(ctx context.Context) ([]int, error) {
 	if len(agb.fields) > 1 {
 		return nil, errors.New("ent: AlertGroupBy.Ints is not achievable when grouping more than 1 field")
@@ -765,7 +787,8 @@ func (agb *AlertGroupBy) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from group-by. It is only allowed when querying group-by with one field.
+// Int returns a single int from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (agb *AlertGroupBy) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = agb.Ints(ctx); err != nil {
@@ -791,7 +814,8 @@ func (agb *AlertGroupBy) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from group-by. It is only allowed when querying group-by with one field.
+// Float64s returns list of float64s from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (agb *AlertGroupBy) Float64s(ctx context.Context) ([]float64, error) {
 	if len(agb.fields) > 1 {
 		return nil, errors.New("ent: AlertGroupBy.Float64s is not achievable when grouping more than 1 field")
@@ -812,7 +836,8 @@ func (agb *AlertGroupBy) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from group-by. It is only allowed when querying group-by with one field.
+// Float64 returns a single float64 from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (agb *AlertGroupBy) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = agb.Float64s(ctx); err != nil {
@@ -838,7 +863,8 @@ func (agb *AlertGroupBy) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from group-by. It is only allowed when querying group-by with one field.
+// Bools returns list of bools from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (agb *AlertGroupBy) Bools(ctx context.Context) ([]bool, error) {
 	if len(agb.fields) > 1 {
 		return nil, errors.New("ent: AlertGroupBy.Bools is not achievable when grouping more than 1 field")
@@ -859,7 +885,8 @@ func (agb *AlertGroupBy) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from group-by. It is only allowed when querying group-by with one field.
+// Bool returns a single bool from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (agb *AlertGroupBy) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = agb.Bools(ctx); err != nil {
@@ -914,22 +941,19 @@ func (agb *AlertGroupBy) sqlQuery() *sql.Selector {
 	return selector.Select(columns...).GroupBy(agb.fields...)
 }
 
-// AlertSelect is the builder for select fields of Alert entities.
+// AlertSelect is the builder for selecting fields of Alert entities.
 type AlertSelect struct {
-	config
-	fields []string
+	*AlertQuery
 	// intermediate query (i.e. traversal path).
-	sql  *sql.Selector
-	path func(context.Context) (*sql.Selector, error)
+	sql *sql.Selector
 }
 
-// Scan applies the selector query and scan the result into the given value.
+// Scan applies the selector query and scans the result into the given value.
 func (as *AlertSelect) Scan(ctx context.Context, v interface{}) error {
-	query, err := as.path(ctx)
-	if err != nil {
+	if err := as.prepareQuery(ctx); err != nil {
 		return err
 	}
-	as.sql = query
+	as.sql = as.AlertQuery.sqlQuery()
 	return as.sqlScan(ctx, v)
 }
 
@@ -940,7 +964,7 @@ func (as *AlertSelect) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from selector. It is only allowed when selecting one field.
+// Strings returns list of strings from a selector. It is only allowed when selecting one field.
 func (as *AlertSelect) Strings(ctx context.Context) ([]string, error) {
 	if len(as.fields) > 1 {
 		return nil, errors.New("ent: AlertSelect.Strings is not achievable when selecting more than 1 field")
@@ -961,7 +985,7 @@ func (as *AlertSelect) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from selector. It is only allowed when selecting one field.
+// String returns a single string from a selector. It is only allowed when selecting one field.
 func (as *AlertSelect) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = as.Strings(ctx); err != nil {
@@ -987,7 +1011,7 @@ func (as *AlertSelect) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from selector. It is only allowed when selecting one field.
+// Ints returns list of ints from a selector. It is only allowed when selecting one field.
 func (as *AlertSelect) Ints(ctx context.Context) ([]int, error) {
 	if len(as.fields) > 1 {
 		return nil, errors.New("ent: AlertSelect.Ints is not achievable when selecting more than 1 field")
@@ -1008,7 +1032,7 @@ func (as *AlertSelect) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from selector. It is only allowed when selecting one field.
+// Int returns a single int from a selector. It is only allowed when selecting one field.
 func (as *AlertSelect) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = as.Ints(ctx); err != nil {
@@ -1034,7 +1058,7 @@ func (as *AlertSelect) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from selector. It is only allowed when selecting one field.
+// Float64s returns list of float64s from a selector. It is only allowed when selecting one field.
 func (as *AlertSelect) Float64s(ctx context.Context) ([]float64, error) {
 	if len(as.fields) > 1 {
 		return nil, errors.New("ent: AlertSelect.Float64s is not achievable when selecting more than 1 field")
@@ -1055,7 +1079,7 @@ func (as *AlertSelect) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from selector. It is only allowed when selecting one field.
+// Float64 returns a single float64 from a selector. It is only allowed when selecting one field.
 func (as *AlertSelect) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = as.Float64s(ctx); err != nil {
@@ -1081,7 +1105,7 @@ func (as *AlertSelect) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from selector. It is only allowed when selecting one field.
+// Bools returns list of bools from a selector. It is only allowed when selecting one field.
 func (as *AlertSelect) Bools(ctx context.Context) ([]bool, error) {
 	if len(as.fields) > 1 {
 		return nil, errors.New("ent: AlertSelect.Bools is not achievable when selecting more than 1 field")
@@ -1102,7 +1126,7 @@ func (as *AlertSelect) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from selector. It is only allowed when selecting one field.
+// Bool returns a single bool from a selector. It is only allowed when selecting one field.
 func (as *AlertSelect) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = as.Bools(ctx); err != nil {
@@ -1129,11 +1153,6 @@ func (as *AlertSelect) BoolX(ctx context.Context) bool {
 }
 
 func (as *AlertSelect) sqlScan(ctx context.Context, v interface{}) error {
-	for _, f := range as.fields {
-		if !alert.ValidColumn(f) {
-			return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for selection", f)}
-		}
-	}
 	rows := &sql.Rows{}
 	query, args := as.sqlQuery().Query()
 	if err := as.driver.Query(ctx, query, args, rows); err != nil {

File diff suppressed because it is too large
+ 132 - 133
pkg/database/ent/alert_update.go


+ 90 - 73
pkg/database/ent/bouncer.go

@@ -39,96 +39,113 @@ type Bouncer struct {
 }
 
 // scanValues returns the types for scanning values from sql.Rows.
-func (*Bouncer) scanValues() []interface{} {
-	return []interface{}{
-		&sql.NullInt64{},  // id
-		&sql.NullTime{},   // created_at
-		&sql.NullTime{},   // updated_at
-		&sql.NullString{}, // name
-		&sql.NullString{}, // api_key
-		&sql.NullBool{},   // revoked
-		&sql.NullString{}, // ip_address
-		&sql.NullString{}, // type
-		&sql.NullString{}, // version
-		&sql.NullTime{},   // until
-		&sql.NullTime{},   // last_pull
+func (*Bouncer) scanValues(columns []string) ([]interface{}, error) {
+	values := make([]interface{}, len(columns))
+	for i := range columns {
+		switch columns[i] {
+		case bouncer.FieldRevoked:
+			values[i] = &sql.NullBool{}
+		case bouncer.FieldID:
+			values[i] = &sql.NullInt64{}
+		case bouncer.FieldName, bouncer.FieldAPIKey, bouncer.FieldIPAddress, bouncer.FieldType, bouncer.FieldVersion:
+			values[i] = &sql.NullString{}
+		case bouncer.FieldCreatedAt, bouncer.FieldUpdatedAt, bouncer.FieldUntil, bouncer.FieldLastPull:
+			values[i] = &sql.NullTime{}
+		default:
+			return nil, fmt.Errorf("unexpected column %q for type Bouncer", columns[i])
+		}
 	}
+	return values, nil
 }
 
 // assignValues assigns the values that were returned from sql.Rows (after scanning)
 // to the Bouncer fields.
-func (b *Bouncer) assignValues(values ...interface{}) error {
-	if m, n := len(values), len(bouncer.Columns); m < n {
+func (b *Bouncer) assignValues(columns []string, values []interface{}) error {
+	if m, n := len(values), len(columns); m < n {
 		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
 	}
-	value, ok := values[0].(*sql.NullInt64)
-	if !ok {
-		return fmt.Errorf("unexpected type %T for field id", value)
-	}
-	b.ID = int(value.Int64)
-	values = values[1:]
-	if value, ok := values[0].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field created_at", values[0])
-	} else if value.Valid {
-		b.CreatedAt = value.Time
-	}
-	if value, ok := values[1].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field updated_at", values[1])
-	} else if value.Valid {
-		b.UpdatedAt = value.Time
-	}
-	if value, ok := values[2].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field name", values[2])
-	} else if value.Valid {
-		b.Name = value.String
-	}
-	if value, ok := values[3].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field api_key", values[3])
-	} else if value.Valid {
-		b.APIKey = value.String
-	}
-	if value, ok := values[4].(*sql.NullBool); !ok {
-		return fmt.Errorf("unexpected type %T for field revoked", values[4])
-	} else if value.Valid {
-		b.Revoked = value.Bool
-	}
-	if value, ok := values[5].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field ip_address", values[5])
-	} else if value.Valid {
-		b.IPAddress = value.String
-	}
-	if value, ok := values[6].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field type", values[6])
-	} else if value.Valid {
-		b.Type = value.String
-	}
-	if value, ok := values[7].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field version", values[7])
-	} else if value.Valid {
-		b.Version = value.String
-	}
-	if value, ok := values[8].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field until", values[8])
-	} else if value.Valid {
-		b.Until = value.Time
-	}
-	if value, ok := values[9].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field last_pull", values[9])
-	} else if value.Valid {
-		b.LastPull = value.Time
+	for i := range columns {
+		switch columns[i] {
+		case bouncer.FieldID:
+			value, ok := values[i].(*sql.NullInt64)
+			if !ok {
+				return fmt.Errorf("unexpected type %T for field id", value)
+			}
+			b.ID = int(value.Int64)
+		case bouncer.FieldCreatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field created_at", values[i])
+			} else if value.Valid {
+				b.CreatedAt = value.Time
+			}
+		case bouncer.FieldUpdatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field updated_at", values[i])
+			} else if value.Valid {
+				b.UpdatedAt = value.Time
+			}
+		case bouncer.FieldName:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field name", values[i])
+			} else if value.Valid {
+				b.Name = value.String
+			}
+		case bouncer.FieldAPIKey:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field api_key", values[i])
+			} else if value.Valid {
+				b.APIKey = value.String
+			}
+		case bouncer.FieldRevoked:
+			if value, ok := values[i].(*sql.NullBool); !ok {
+				return fmt.Errorf("unexpected type %T for field revoked", values[i])
+			} else if value.Valid {
+				b.Revoked = value.Bool
+			}
+		case bouncer.FieldIPAddress:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field ip_address", values[i])
+			} else if value.Valid {
+				b.IPAddress = value.String
+			}
+		case bouncer.FieldType:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field type", values[i])
+			} else if value.Valid {
+				b.Type = value.String
+			}
+		case bouncer.FieldVersion:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field version", values[i])
+			} else if value.Valid {
+				b.Version = value.String
+			}
+		case bouncer.FieldUntil:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field until", values[i])
+			} else if value.Valid {
+				b.Until = value.Time
+			}
+		case bouncer.FieldLastPull:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field last_pull", values[i])
+			} else if value.Valid {
+				b.LastPull = value.Time
+			}
+		}
 	}
 	return nil
 }
 
 // Update returns a builder for updating this Bouncer.
-// Note that, you need to call Bouncer.Unwrap() before calling this method, if this Bouncer
+// Note that you need to call Bouncer.Unwrap() before calling this method if this Bouncer
 // was returned from a transaction, and the transaction was committed or rolled back.
 func (b *Bouncer) Update() *BouncerUpdateOne {
 	return (&BouncerClient{config: b.config}).UpdateOne(b)
 }
 
-// Unwrap unwraps the entity that was returned from a transaction after it was closed,
-// so that all next queries will be executed through the driver which created the transaction.
+// Unwrap unwraps the Bouncer entity that was returned from a transaction after it was closed,
+// so that all future queries will be executed through the driver which created the transaction.
 func (b *Bouncer) Unwrap() *Bouncer {
 	tx, ok := b.config.driver.(*txDriver)
 	if !ok {

+ 5 - 5
pkg/database/ent/bouncer/bouncer.go

@@ -62,14 +62,14 @@ func ValidColumn(column string) bool {
 }
 
 var (
-	// DefaultCreatedAt holds the default value on creation for the created_at field.
+	// DefaultCreatedAt holds the default value on creation for the "created_at" field.
 	DefaultCreatedAt func() time.Time
-	// DefaultUpdatedAt holds the default value on creation for the updated_at field.
+	// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
 	DefaultUpdatedAt func() time.Time
-	// DefaultIPAddress holds the default value on creation for the ip_address field.
+	// DefaultIPAddress holds the default value on creation for the "ip_address" field.
 	DefaultIPAddress string
-	// DefaultUntil holds the default value on creation for the until field.
+	// DefaultUntil holds the default value on creation for the "until" field.
 	DefaultUntil func() time.Time
-	// DefaultLastPull holds the default value on creation for the last_pull field.
+	// DefaultLastPull holds the default value on creation for the "last_pull" field.
 	DefaultLastPull func() time.Time
 )

+ 3 - 3
pkg/database/ent/bouncer/where.go

@@ -9,7 +9,7 @@ import (
 	"github.com/facebook/ent/dialect/sql"
 )
 
-// ID filters vertices based on their identifier.
+// ID filters vertices based on their ID field.
 func ID(id int) predicate.Bouncer {
 	return predicate.Bouncer(func(s *sql.Selector) {
 		s.Where(sql.EQ(s.C(FieldID), id))
@@ -1091,7 +1091,7 @@ func LastPullLTE(v time.Time) predicate.Bouncer {
 	})
 }
 
-// And groups list of predicates with the AND operator between them.
+// And groups predicates with the AND operator between them.
 func And(predicates ...predicate.Bouncer) predicate.Bouncer {
 	return predicate.Bouncer(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)
@@ -1102,7 +1102,7 @@ func And(predicates ...predicate.Bouncer) predicate.Bouncer {
 	})
 }
 
-// Or groups list of predicates with the OR operator between them.
+// Or groups predicates with the OR operator between them.
 func Or(predicates ...predicate.Bouncer) predicate.Bouncer {
 	return predicate.Bouncer(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)

+ 19 - 19
pkg/database/ent/bouncer_create.go

@@ -20,13 +20,13 @@ type BouncerCreate struct {
 	hooks    []Hook
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (bc *BouncerCreate) SetCreatedAt(t time.Time) *BouncerCreate {
 	bc.mutation.SetCreatedAt(t)
 	return bc
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (bc *BouncerCreate) SetNillableCreatedAt(t *time.Time) *BouncerCreate {
 	if t != nil {
 		bc.SetCreatedAt(*t)
@@ -34,13 +34,13 @@ func (bc *BouncerCreate) SetNillableCreatedAt(t *time.Time) *BouncerCreate {
 	return bc
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (bc *BouncerCreate) SetUpdatedAt(t time.Time) *BouncerCreate {
 	bc.mutation.SetUpdatedAt(t)
 	return bc
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (bc *BouncerCreate) SetNillableUpdatedAt(t *time.Time) *BouncerCreate {
 	if t != nil {
 		bc.SetUpdatedAt(*t)
@@ -48,31 +48,31 @@ func (bc *BouncerCreate) SetNillableUpdatedAt(t *time.Time) *BouncerCreate {
 	return bc
 }
 
-// SetName sets the name field.
+// SetName sets the "name" field.
 func (bc *BouncerCreate) SetName(s string) *BouncerCreate {
 	bc.mutation.SetName(s)
 	return bc
 }
 
-// SetAPIKey sets the api_key field.
+// SetAPIKey sets the "api_key" field.
 func (bc *BouncerCreate) SetAPIKey(s string) *BouncerCreate {
 	bc.mutation.SetAPIKey(s)
 	return bc
 }
 
-// SetRevoked sets the revoked field.
+// SetRevoked sets the "revoked" field.
 func (bc *BouncerCreate) SetRevoked(b bool) *BouncerCreate {
 	bc.mutation.SetRevoked(b)
 	return bc
 }
 
-// SetIPAddress sets the ip_address field.
+// SetIPAddress sets the "ip_address" field.
 func (bc *BouncerCreate) SetIPAddress(s string) *BouncerCreate {
 	bc.mutation.SetIPAddress(s)
 	return bc
 }
 
-// SetNillableIPAddress sets the ip_address field if the given value is not nil.
+// SetNillableIPAddress sets the "ip_address" field if the given value is not nil.
 func (bc *BouncerCreate) SetNillableIPAddress(s *string) *BouncerCreate {
 	if s != nil {
 		bc.SetIPAddress(*s)
@@ -80,13 +80,13 @@ func (bc *BouncerCreate) SetNillableIPAddress(s *string) *BouncerCreate {
 	return bc
 }
 
-// SetType sets the type field.
+// SetType sets the "type" field.
 func (bc *BouncerCreate) SetType(s string) *BouncerCreate {
 	bc.mutation.SetType(s)
 	return bc
 }
 
-// SetNillableType sets the type field if the given value is not nil.
+// SetNillableType sets the "type" field if the given value is not nil.
 func (bc *BouncerCreate) SetNillableType(s *string) *BouncerCreate {
 	if s != nil {
 		bc.SetType(*s)
@@ -94,13 +94,13 @@ func (bc *BouncerCreate) SetNillableType(s *string) *BouncerCreate {
 	return bc
 }
 
-// SetVersion sets the version field.
+// SetVersion sets the "version" field.
 func (bc *BouncerCreate) SetVersion(s string) *BouncerCreate {
 	bc.mutation.SetVersion(s)
 	return bc
 }
 
-// SetNillableVersion sets the version field if the given value is not nil.
+// SetNillableVersion sets the "version" field if the given value is not nil.
 func (bc *BouncerCreate) SetNillableVersion(s *string) *BouncerCreate {
 	if s != nil {
 		bc.SetVersion(*s)
@@ -108,13 +108,13 @@ func (bc *BouncerCreate) SetNillableVersion(s *string) *BouncerCreate {
 	return bc
 }
 
-// SetUntil sets the until field.
+// SetUntil sets the "until" field.
 func (bc *BouncerCreate) SetUntil(t time.Time) *BouncerCreate {
 	bc.mutation.SetUntil(t)
 	return bc
 }
 
-// SetNillableUntil sets the until field if the given value is not nil.
+// SetNillableUntil sets the "until" field if the given value is not nil.
 func (bc *BouncerCreate) SetNillableUntil(t *time.Time) *BouncerCreate {
 	if t != nil {
 		bc.SetUntil(*t)
@@ -122,13 +122,13 @@ func (bc *BouncerCreate) SetNillableUntil(t *time.Time) *BouncerCreate {
 	return bc
 }
 
-// SetLastPull sets the last_pull field.
+// SetLastPull sets the "last_pull" field.
 func (bc *BouncerCreate) SetLastPull(t time.Time) *BouncerCreate {
 	bc.mutation.SetLastPull(t)
 	return bc
 }
 
-// SetNillableLastPull sets the last_pull field if the given value is not nil.
+// SetNillableLastPull sets the "last_pull" field if the given value is not nil.
 func (bc *BouncerCreate) SetNillableLastPull(t *time.Time) *BouncerCreate {
 	if t != nil {
 		bc.SetLastPull(*t)
@@ -340,7 +340,7 @@ func (bc *BouncerCreate) createSpec() (*Bouncer, *sqlgraph.CreateSpec) {
 	return _node, _spec
 }
 
-// BouncerCreateBulk is the builder for creating a bulk of Bouncer entities.
+// BouncerCreateBulk is the builder for creating many Bouncer entities in bulk.
 type BouncerCreateBulk struct {
 	config
 	builders []*BouncerCreate
@@ -398,7 +398,7 @@ func (bcb *BouncerCreateBulk) Save(ctx context.Context) ([]*Bouncer, error) {
 	return nodes, nil
 }
 
-// SaveX calls Save and panics if Save returns an error.
+// SaveX is like Save, but panics if an error occurs.
 func (bcb *BouncerCreateBulk) SaveX(ctx context.Context) []*Bouncer {
 	v, err := bcb.Save(ctx)
 	if err != nil {

+ 5 - 6
pkg/database/ent/bouncer_delete.go

@@ -16,14 +16,13 @@ import (
 // BouncerDelete is the builder for deleting a Bouncer entity.
 type BouncerDelete struct {
 	config
-	hooks      []Hook
-	mutation   *BouncerMutation
-	predicates []predicate.Bouncer
+	hooks    []Hook
+	mutation *BouncerMutation
 }
 
-// Where adds a new predicate to the delete builder.
+// Where adds a new predicate to the BouncerDelete builder.
 func (bd *BouncerDelete) Where(ps ...predicate.Bouncer) *BouncerDelete {
-	bd.predicates = append(bd.predicates, ps...)
+	bd.mutation.predicates = append(bd.mutation.predicates, ps...)
 	return bd
 }
 
@@ -75,7 +74,7 @@ func (bd *BouncerDelete) sqlExec(ctx context.Context) (int, error) {
 			},
 		},
 	}
-	if ps := bd.predicates; len(ps) > 0 {
+	if ps := bd.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)

+ 74 - 59
pkg/database/ent/bouncer_query.go

@@ -21,14 +21,14 @@ type BouncerQuery struct {
 	limit      *int
 	offset     *int
 	order      []OrderFunc
-	unique     []string
+	fields     []string
 	predicates []predicate.Bouncer
 	// intermediate query (i.e. traversal path).
 	sql  *sql.Selector
 	path func(context.Context) (*sql.Selector, error)
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the BouncerQuery builder.
 func (bq *BouncerQuery) Where(ps ...predicate.Bouncer) *BouncerQuery {
 	bq.predicates = append(bq.predicates, ps...)
 	return bq
@@ -52,7 +52,8 @@ func (bq *BouncerQuery) Order(o ...OrderFunc) *BouncerQuery {
 	return bq
 }
 
-// First returns the first Bouncer entity in the query. Returns *NotFoundError when no bouncer was found.
+// First returns the first Bouncer entity from the query.
+// Returns a *NotFoundError when no Bouncer was found.
 func (bq *BouncerQuery) First(ctx context.Context) (*Bouncer, error) {
 	nodes, err := bq.Limit(1).All(ctx)
 	if err != nil {
@@ -73,7 +74,8 @@ func (bq *BouncerQuery) FirstX(ctx context.Context) *Bouncer {
 	return node
 }
 
-// FirstID returns the first Bouncer id in the query. Returns *NotFoundError when no id was found.
+// FirstID returns the first Bouncer ID from the query.
+// Returns a *NotFoundError when no Bouncer ID was found.
 func (bq *BouncerQuery) FirstID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = bq.Limit(1).IDs(ctx); err != nil {
@@ -86,8 +88,8 @@ func (bq *BouncerQuery) FirstID(ctx context.Context) (id int, err error) {
 	return ids[0], nil
 }
 
-// FirstXID is like FirstID, but panics if an error occurs.
-func (bq *BouncerQuery) FirstXID(ctx context.Context) int {
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (bq *BouncerQuery) FirstIDX(ctx context.Context) int {
 	id, err := bq.FirstID(ctx)
 	if err != nil && !IsNotFound(err) {
 		panic(err)
@@ -95,7 +97,9 @@ func (bq *BouncerQuery) FirstXID(ctx context.Context) int {
 	return id
 }
 
-// Only returns the only Bouncer entity in the query, returns an error if not exactly one entity was returned.
+// Only returns a single Bouncer entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when exactly one Bouncer entity is not found.
+// Returns a *NotFoundError when no Bouncer entities are found.
 func (bq *BouncerQuery) Only(ctx context.Context) (*Bouncer, error) {
 	nodes, err := bq.Limit(2).All(ctx)
 	if err != nil {
@@ -120,7 +124,9 @@ func (bq *BouncerQuery) OnlyX(ctx context.Context) *Bouncer {
 	return node
 }
 
-// OnlyID returns the only Bouncer id in the query, returns an error if not exactly one id was returned.
+// OnlyID is like Only, but returns the only Bouncer ID in the query.
+// Returns a *NotSingularError when exactly one Bouncer ID is not found.
+// Returns a *NotFoundError when no entities are found.
 func (bq *BouncerQuery) OnlyID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = bq.Limit(2).IDs(ctx); err != nil {
@@ -163,7 +169,7 @@ func (bq *BouncerQuery) AllX(ctx context.Context) []*Bouncer {
 	return nodes
 }
 
-// IDs executes the query and returns a list of Bouncer ids.
+// IDs executes the query and returns a list of Bouncer IDs.
 func (bq *BouncerQuery) IDs(ctx context.Context) ([]int, error) {
 	var ids []int
 	if err := bq.Select(bouncer.FieldID).Scan(ctx, &ids); err != nil {
@@ -215,15 +221,17 @@ func (bq *BouncerQuery) ExistX(ctx context.Context) bool {
 	return exist
 }
 
-// Clone returns a duplicate of the query builder, including all associated steps. It can be
+// Clone returns a duplicate of the BouncerQuery builder, including all associated steps. It can be
 // used to prepare common query builders and use them differently after the clone is made.
 func (bq *BouncerQuery) Clone() *BouncerQuery {
+	if bq == nil {
+		return nil
+	}
 	return &BouncerQuery{
 		config:     bq.config,
 		limit:      bq.limit,
 		offset:     bq.offset,
 		order:      append([]OrderFunc{}, bq.order...),
-		unique:     append([]string{}, bq.unique...),
 		predicates: append([]predicate.Bouncer{}, bq.predicates...),
 		// clone intermediate query.
 		sql:  bq.sql.Clone(),
@@ -231,7 +239,7 @@ func (bq *BouncerQuery) Clone() *BouncerQuery {
 	}
 }
 
-// GroupBy used to group vertices by one or more fields/columns.
+// GroupBy is used to group vertices by one or more fields/columns.
 // It is often used with aggregate functions, like: count, max, mean, min, sum.
 //
 // Example:
@@ -258,7 +266,8 @@ func (bq *BouncerQuery) GroupBy(field string, fields ...string) *BouncerGroupBy
 	return group
 }
 
-// Select one or more fields from the given query.
+// Select allows the selection one or more fields/columns for the given query,
+// instead of selecting all fields in the entity.
 //
 // Example:
 //
@@ -271,18 +280,16 @@ func (bq *BouncerQuery) GroupBy(field string, fields ...string) *BouncerGroupBy
 //		Scan(ctx, &v)
 //
 func (bq *BouncerQuery) Select(field string, fields ...string) *BouncerSelect {
-	selector := &BouncerSelect{config: bq.config}
-	selector.fields = append([]string{field}, fields...)
-	selector.path = func(ctx context.Context) (prev *sql.Selector, err error) {
-		if err := bq.prepareQuery(ctx); err != nil {
-			return nil, err
-		}
-		return bq.sqlQuery(), nil
-	}
-	return selector
+	bq.fields = append([]string{field}, fields...)
+	return &BouncerSelect{BouncerQuery: bq}
 }
 
 func (bq *BouncerQuery) prepareQuery(ctx context.Context) error {
+	for _, f := range bq.fields {
+		if !bouncer.ValidColumn(f) {
+			return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+		}
+	}
 	if bq.path != nil {
 		prev, err := bq.path(ctx)
 		if err != nil {
@@ -298,18 +305,17 @@ func (bq *BouncerQuery) sqlAll(ctx context.Context) ([]*Bouncer, error) {
 		nodes = []*Bouncer{}
 		_spec = bq.querySpec()
 	)
-	_spec.ScanValues = func() []interface{} {
+	_spec.ScanValues = func(columns []string) ([]interface{}, error) {
 		node := &Bouncer{config: bq.config}
 		nodes = append(nodes, node)
-		values := node.scanValues()
-		return values
+		return node.scanValues(columns)
 	}
-	_spec.Assign = func(values ...interface{}) error {
+	_spec.Assign = func(columns []string, values []interface{}) error {
 		if len(nodes) == 0 {
 			return fmt.Errorf("ent: Assign called without calling ScanValues")
 		}
 		node := nodes[len(nodes)-1]
-		return node.assignValues(values...)
+		return node.assignValues(columns, values)
 	}
 	if err := sqlgraph.QueryNodes(ctx, bq.driver, _spec); err != nil {
 		return nil, err
@@ -346,6 +352,15 @@ func (bq *BouncerQuery) querySpec() *sqlgraph.QuerySpec {
 		From:   bq.sql,
 		Unique: true,
 	}
+	if fields := bq.fields; len(fields) > 0 {
+		_spec.Node.Columns = make([]string, 0, len(fields))
+		_spec.Node.Columns = append(_spec.Node.Columns, bouncer.FieldID)
+		for i := range fields {
+			if fields[i] != bouncer.FieldID {
+				_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+			}
+		}
+	}
 	if ps := bq.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
@@ -394,7 +409,7 @@ func (bq *BouncerQuery) sqlQuery() *sql.Selector {
 	return selector
 }
 
-// BouncerGroupBy is the builder for group-by Bouncer entities.
+// BouncerGroupBy is the group-by builder for Bouncer entities.
 type BouncerGroupBy struct {
 	config
 	fields []string
@@ -410,7 +425,7 @@ func (bgb *BouncerGroupBy) Aggregate(fns ...AggregateFunc) *BouncerGroupBy {
 	return bgb
 }
 
-// Scan applies the group-by query and scan the result into the given value.
+// Scan applies the group-by query and scans the result into the given value.
 func (bgb *BouncerGroupBy) Scan(ctx context.Context, v interface{}) error {
 	query, err := bgb.path(ctx)
 	if err != nil {
@@ -427,7 +442,8 @@ func (bgb *BouncerGroupBy) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from group-by. It is only allowed when querying group-by with one field.
+// Strings returns list of strings from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (bgb *BouncerGroupBy) Strings(ctx context.Context) ([]string, error) {
 	if len(bgb.fields) > 1 {
 		return nil, errors.New("ent: BouncerGroupBy.Strings is not achievable when grouping more than 1 field")
@@ -448,7 +464,8 @@ func (bgb *BouncerGroupBy) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from group-by. It is only allowed when querying group-by with one field.
+// String returns a single string from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (bgb *BouncerGroupBy) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = bgb.Strings(ctx); err != nil {
@@ -474,7 +491,8 @@ func (bgb *BouncerGroupBy) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from group-by. It is only allowed when querying group-by with one field.
+// Ints returns list of ints from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (bgb *BouncerGroupBy) Ints(ctx context.Context) ([]int, error) {
 	if len(bgb.fields) > 1 {
 		return nil, errors.New("ent: BouncerGroupBy.Ints is not achievable when grouping more than 1 field")
@@ -495,7 +513,8 @@ func (bgb *BouncerGroupBy) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from group-by. It is only allowed when querying group-by with one field.
+// Int returns a single int from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (bgb *BouncerGroupBy) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = bgb.Ints(ctx); err != nil {
@@ -521,7 +540,8 @@ func (bgb *BouncerGroupBy) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from group-by. It is only allowed when querying group-by with one field.
+// Float64s returns list of float64s from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (bgb *BouncerGroupBy) Float64s(ctx context.Context) ([]float64, error) {
 	if len(bgb.fields) > 1 {
 		return nil, errors.New("ent: BouncerGroupBy.Float64s is not achievable when grouping more than 1 field")
@@ -542,7 +562,8 @@ func (bgb *BouncerGroupBy) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from group-by. It is only allowed when querying group-by with one field.
+// Float64 returns a single float64 from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (bgb *BouncerGroupBy) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = bgb.Float64s(ctx); err != nil {
@@ -568,7 +589,8 @@ func (bgb *BouncerGroupBy) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from group-by. It is only allowed when querying group-by with one field.
+// Bools returns list of bools from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (bgb *BouncerGroupBy) Bools(ctx context.Context) ([]bool, error) {
 	if len(bgb.fields) > 1 {
 		return nil, errors.New("ent: BouncerGroupBy.Bools is not achievable when grouping more than 1 field")
@@ -589,7 +611,8 @@ func (bgb *BouncerGroupBy) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from group-by. It is only allowed when querying group-by with one field.
+// Bool returns a single bool from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (bgb *BouncerGroupBy) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = bgb.Bools(ctx); err != nil {
@@ -644,22 +667,19 @@ func (bgb *BouncerGroupBy) sqlQuery() *sql.Selector {
 	return selector.Select(columns...).GroupBy(bgb.fields...)
 }
 
-// BouncerSelect is the builder for select fields of Bouncer entities.
+// BouncerSelect is the builder for selecting fields of Bouncer entities.
 type BouncerSelect struct {
-	config
-	fields []string
+	*BouncerQuery
 	// intermediate query (i.e. traversal path).
-	sql  *sql.Selector
-	path func(context.Context) (*sql.Selector, error)
+	sql *sql.Selector
 }
 
-// Scan applies the selector query and scan the result into the given value.
+// Scan applies the selector query and scans the result into the given value.
 func (bs *BouncerSelect) Scan(ctx context.Context, v interface{}) error {
-	query, err := bs.path(ctx)
-	if err != nil {
+	if err := bs.prepareQuery(ctx); err != nil {
 		return err
 	}
-	bs.sql = query
+	bs.sql = bs.BouncerQuery.sqlQuery()
 	return bs.sqlScan(ctx, v)
 }
 
@@ -670,7 +690,7 @@ func (bs *BouncerSelect) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from selector. It is only allowed when selecting one field.
+// Strings returns list of strings from a selector. It is only allowed when selecting one field.
 func (bs *BouncerSelect) Strings(ctx context.Context) ([]string, error) {
 	if len(bs.fields) > 1 {
 		return nil, errors.New("ent: BouncerSelect.Strings is not achievable when selecting more than 1 field")
@@ -691,7 +711,7 @@ func (bs *BouncerSelect) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from selector. It is only allowed when selecting one field.
+// String returns a single string from a selector. It is only allowed when selecting one field.
 func (bs *BouncerSelect) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = bs.Strings(ctx); err != nil {
@@ -717,7 +737,7 @@ func (bs *BouncerSelect) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from selector. It is only allowed when selecting one field.
+// Ints returns list of ints from a selector. It is only allowed when selecting one field.
 func (bs *BouncerSelect) Ints(ctx context.Context) ([]int, error) {
 	if len(bs.fields) > 1 {
 		return nil, errors.New("ent: BouncerSelect.Ints is not achievable when selecting more than 1 field")
@@ -738,7 +758,7 @@ func (bs *BouncerSelect) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from selector. It is only allowed when selecting one field.
+// Int returns a single int from a selector. It is only allowed when selecting one field.
 func (bs *BouncerSelect) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = bs.Ints(ctx); err != nil {
@@ -764,7 +784,7 @@ func (bs *BouncerSelect) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from selector. It is only allowed when selecting one field.
+// Float64s returns list of float64s from a selector. It is only allowed when selecting one field.
 func (bs *BouncerSelect) Float64s(ctx context.Context) ([]float64, error) {
 	if len(bs.fields) > 1 {
 		return nil, errors.New("ent: BouncerSelect.Float64s is not achievable when selecting more than 1 field")
@@ -785,7 +805,7 @@ func (bs *BouncerSelect) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from selector. It is only allowed when selecting one field.
+// Float64 returns a single float64 from a selector. It is only allowed when selecting one field.
 func (bs *BouncerSelect) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = bs.Float64s(ctx); err != nil {
@@ -811,7 +831,7 @@ func (bs *BouncerSelect) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from selector. It is only allowed when selecting one field.
+// Bools returns list of bools from a selector. It is only allowed when selecting one field.
 func (bs *BouncerSelect) Bools(ctx context.Context) ([]bool, error) {
 	if len(bs.fields) > 1 {
 		return nil, errors.New("ent: BouncerSelect.Bools is not achievable when selecting more than 1 field")
@@ -832,7 +852,7 @@ func (bs *BouncerSelect) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from selector. It is only allowed when selecting one field.
+// Bool returns a single bool from a selector. It is only allowed when selecting one field.
 func (bs *BouncerSelect) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = bs.Bools(ctx); err != nil {
@@ -859,11 +879,6 @@ func (bs *BouncerSelect) BoolX(ctx context.Context) bool {
 }
 
 func (bs *BouncerSelect) sqlScan(ctx context.Context, v interface{}) error {
-	for _, f := range bs.fields {
-		if !bouncer.ValidColumn(f) {
-			return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for selection", f)}
-		}
-	}
 	rows := &sql.Rows{}
 	query, args := bs.sqlQuery().Query()
 	if err := bs.driver.Query(ctx, query, args, rows); err != nil {

+ 50 - 51
pkg/database/ent/bouncer_update.go

@@ -17,24 +17,23 @@ import (
 // BouncerUpdate is the builder for updating Bouncer entities.
 type BouncerUpdate struct {
 	config
-	hooks      []Hook
-	mutation   *BouncerMutation
-	predicates []predicate.Bouncer
+	hooks    []Hook
+	mutation *BouncerMutation
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the BouncerUpdate builder.
 func (bu *BouncerUpdate) Where(ps ...predicate.Bouncer) *BouncerUpdate {
-	bu.predicates = append(bu.predicates, ps...)
+	bu.mutation.predicates = append(bu.mutation.predicates, ps...)
 	return bu
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (bu *BouncerUpdate) SetCreatedAt(t time.Time) *BouncerUpdate {
 	bu.mutation.SetCreatedAt(t)
 	return bu
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (bu *BouncerUpdate) SetNillableCreatedAt(t *time.Time) *BouncerUpdate {
 	if t != nil {
 		bu.SetCreatedAt(*t)
@@ -42,13 +41,13 @@ func (bu *BouncerUpdate) SetNillableCreatedAt(t *time.Time) *BouncerUpdate {
 	return bu
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (bu *BouncerUpdate) SetUpdatedAt(t time.Time) *BouncerUpdate {
 	bu.mutation.SetUpdatedAt(t)
 	return bu
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (bu *BouncerUpdate) SetNillableUpdatedAt(t *time.Time) *BouncerUpdate {
 	if t != nil {
 		bu.SetUpdatedAt(*t)
@@ -56,31 +55,31 @@ func (bu *BouncerUpdate) SetNillableUpdatedAt(t *time.Time) *BouncerUpdate {
 	return bu
 }
 
-// SetName sets the name field.
+// SetName sets the "name" field.
 func (bu *BouncerUpdate) SetName(s string) *BouncerUpdate {
 	bu.mutation.SetName(s)
 	return bu
 }
 
-// SetAPIKey sets the api_key field.
+// SetAPIKey sets the "api_key" field.
 func (bu *BouncerUpdate) SetAPIKey(s string) *BouncerUpdate {
 	bu.mutation.SetAPIKey(s)
 	return bu
 }
 
-// SetRevoked sets the revoked field.
+// SetRevoked sets the "revoked" field.
 func (bu *BouncerUpdate) SetRevoked(b bool) *BouncerUpdate {
 	bu.mutation.SetRevoked(b)
 	return bu
 }
 
-// SetIPAddress sets the ip_address field.
+// SetIPAddress sets the "ip_address" field.
 func (bu *BouncerUpdate) SetIPAddress(s string) *BouncerUpdate {
 	bu.mutation.SetIPAddress(s)
 	return bu
 }
 
-// SetNillableIPAddress sets the ip_address field if the given value is not nil.
+// SetNillableIPAddress sets the "ip_address" field if the given value is not nil.
 func (bu *BouncerUpdate) SetNillableIPAddress(s *string) *BouncerUpdate {
 	if s != nil {
 		bu.SetIPAddress(*s)
@@ -88,19 +87,19 @@ func (bu *BouncerUpdate) SetNillableIPAddress(s *string) *BouncerUpdate {
 	return bu
 }
 
-// ClearIPAddress clears the value of ip_address.
+// ClearIPAddress clears the value of the "ip_address" field.
 func (bu *BouncerUpdate) ClearIPAddress() *BouncerUpdate {
 	bu.mutation.ClearIPAddress()
 	return bu
 }
 
-// SetType sets the type field.
+// SetType sets the "type" field.
 func (bu *BouncerUpdate) SetType(s string) *BouncerUpdate {
 	bu.mutation.SetType(s)
 	return bu
 }
 
-// SetNillableType sets the type field if the given value is not nil.
+// SetNillableType sets the "type" field if the given value is not nil.
 func (bu *BouncerUpdate) SetNillableType(s *string) *BouncerUpdate {
 	if s != nil {
 		bu.SetType(*s)
@@ -108,19 +107,19 @@ func (bu *BouncerUpdate) SetNillableType(s *string) *BouncerUpdate {
 	return bu
 }
 
-// ClearType clears the value of type.
+// ClearType clears the value of the "type" field.
 func (bu *BouncerUpdate) ClearType() *BouncerUpdate {
 	bu.mutation.ClearType()
 	return bu
 }
 
-// SetVersion sets the version field.
+// SetVersion sets the "version" field.
 func (bu *BouncerUpdate) SetVersion(s string) *BouncerUpdate {
 	bu.mutation.SetVersion(s)
 	return bu
 }
 
-// SetNillableVersion sets the version field if the given value is not nil.
+// SetNillableVersion sets the "version" field if the given value is not nil.
 func (bu *BouncerUpdate) SetNillableVersion(s *string) *BouncerUpdate {
 	if s != nil {
 		bu.SetVersion(*s)
@@ -128,19 +127,19 @@ func (bu *BouncerUpdate) SetNillableVersion(s *string) *BouncerUpdate {
 	return bu
 }
 
-// ClearVersion clears the value of version.
+// ClearVersion clears the value of the "version" field.
 func (bu *BouncerUpdate) ClearVersion() *BouncerUpdate {
 	bu.mutation.ClearVersion()
 	return bu
 }
 
-// SetUntil sets the until field.
+// SetUntil sets the "until" field.
 func (bu *BouncerUpdate) SetUntil(t time.Time) *BouncerUpdate {
 	bu.mutation.SetUntil(t)
 	return bu
 }
 
-// SetNillableUntil sets the until field if the given value is not nil.
+// SetNillableUntil sets the "until" field if the given value is not nil.
 func (bu *BouncerUpdate) SetNillableUntil(t *time.Time) *BouncerUpdate {
 	if t != nil {
 		bu.SetUntil(*t)
@@ -148,19 +147,19 @@ func (bu *BouncerUpdate) SetNillableUntil(t *time.Time) *BouncerUpdate {
 	return bu
 }
 
-// ClearUntil clears the value of until.
+// ClearUntil clears the value of the "until" field.
 func (bu *BouncerUpdate) ClearUntil() *BouncerUpdate {
 	bu.mutation.ClearUntil()
 	return bu
 }
 
-// SetLastPull sets the last_pull field.
+// SetLastPull sets the "last_pull" field.
 func (bu *BouncerUpdate) SetLastPull(t time.Time) *BouncerUpdate {
 	bu.mutation.SetLastPull(t)
 	return bu
 }
 
-// SetNillableLastPull sets the last_pull field if the given value is not nil.
+// SetNillableLastPull sets the "last_pull" field if the given value is not nil.
 func (bu *BouncerUpdate) SetNillableLastPull(t *time.Time) *BouncerUpdate {
 	if t != nil {
 		bu.SetLastPull(*t)
@@ -173,7 +172,7 @@ func (bu *BouncerUpdate) Mutation() *BouncerMutation {
 	return bu.mutation
 }
 
-// Save executes the query and returns the number of rows/vertices matched by this operation.
+// Save executes the query and returns the number of nodes affected by the update operation.
 func (bu *BouncerUpdate) Save(ctx context.Context) (int, error) {
 	var (
 		err      error
@@ -235,7 +234,7 @@ func (bu *BouncerUpdate) sqlSave(ctx context.Context) (n int, err error) {
 			},
 		},
 	}
-	if ps := bu.predicates; len(ps) > 0 {
+	if ps := bu.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)
@@ -354,13 +353,13 @@ type BouncerUpdateOne struct {
 	mutation *BouncerMutation
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (buo *BouncerUpdateOne) SetCreatedAt(t time.Time) *BouncerUpdateOne {
 	buo.mutation.SetCreatedAt(t)
 	return buo
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (buo *BouncerUpdateOne) SetNillableCreatedAt(t *time.Time) *BouncerUpdateOne {
 	if t != nil {
 		buo.SetCreatedAt(*t)
@@ -368,13 +367,13 @@ func (buo *BouncerUpdateOne) SetNillableCreatedAt(t *time.Time) *BouncerUpdateOn
 	return buo
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (buo *BouncerUpdateOne) SetUpdatedAt(t time.Time) *BouncerUpdateOne {
 	buo.mutation.SetUpdatedAt(t)
 	return buo
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (buo *BouncerUpdateOne) SetNillableUpdatedAt(t *time.Time) *BouncerUpdateOne {
 	if t != nil {
 		buo.SetUpdatedAt(*t)
@@ -382,31 +381,31 @@ func (buo *BouncerUpdateOne) SetNillableUpdatedAt(t *time.Time) *BouncerUpdateOn
 	return buo
 }
 
-// SetName sets the name field.
+// SetName sets the "name" field.
 func (buo *BouncerUpdateOne) SetName(s string) *BouncerUpdateOne {
 	buo.mutation.SetName(s)
 	return buo
 }
 
-// SetAPIKey sets the api_key field.
+// SetAPIKey sets the "api_key" field.
 func (buo *BouncerUpdateOne) SetAPIKey(s string) *BouncerUpdateOne {
 	buo.mutation.SetAPIKey(s)
 	return buo
 }
 
-// SetRevoked sets the revoked field.
+// SetRevoked sets the "revoked" field.
 func (buo *BouncerUpdateOne) SetRevoked(b bool) *BouncerUpdateOne {
 	buo.mutation.SetRevoked(b)
 	return buo
 }
 
-// SetIPAddress sets the ip_address field.
+// SetIPAddress sets the "ip_address" field.
 func (buo *BouncerUpdateOne) SetIPAddress(s string) *BouncerUpdateOne {
 	buo.mutation.SetIPAddress(s)
 	return buo
 }
 
-// SetNillableIPAddress sets the ip_address field if the given value is not nil.
+// SetNillableIPAddress sets the "ip_address" field if the given value is not nil.
 func (buo *BouncerUpdateOne) SetNillableIPAddress(s *string) *BouncerUpdateOne {
 	if s != nil {
 		buo.SetIPAddress(*s)
@@ -414,19 +413,19 @@ func (buo *BouncerUpdateOne) SetNillableIPAddress(s *string) *BouncerUpdateOne {
 	return buo
 }
 
-// ClearIPAddress clears the value of ip_address.
+// ClearIPAddress clears the value of the "ip_address" field.
 func (buo *BouncerUpdateOne) ClearIPAddress() *BouncerUpdateOne {
 	buo.mutation.ClearIPAddress()
 	return buo
 }
 
-// SetType sets the type field.
+// SetType sets the "type" field.
 func (buo *BouncerUpdateOne) SetType(s string) *BouncerUpdateOne {
 	buo.mutation.SetType(s)
 	return buo
 }
 
-// SetNillableType sets the type field if the given value is not nil.
+// SetNillableType sets the "type" field if the given value is not nil.
 func (buo *BouncerUpdateOne) SetNillableType(s *string) *BouncerUpdateOne {
 	if s != nil {
 		buo.SetType(*s)
@@ -434,19 +433,19 @@ func (buo *BouncerUpdateOne) SetNillableType(s *string) *BouncerUpdateOne {
 	return buo
 }
 
-// ClearType clears the value of type.
+// ClearType clears the value of the "type" field.
 func (buo *BouncerUpdateOne) ClearType() *BouncerUpdateOne {
 	buo.mutation.ClearType()
 	return buo
 }
 
-// SetVersion sets the version field.
+// SetVersion sets the "version" field.
 func (buo *BouncerUpdateOne) SetVersion(s string) *BouncerUpdateOne {
 	buo.mutation.SetVersion(s)
 	return buo
 }
 
-// SetNillableVersion sets the version field if the given value is not nil.
+// SetNillableVersion sets the "version" field if the given value is not nil.
 func (buo *BouncerUpdateOne) SetNillableVersion(s *string) *BouncerUpdateOne {
 	if s != nil {
 		buo.SetVersion(*s)
@@ -454,19 +453,19 @@ func (buo *BouncerUpdateOne) SetNillableVersion(s *string) *BouncerUpdateOne {
 	return buo
 }
 
-// ClearVersion clears the value of version.
+// ClearVersion clears the value of the "version" field.
 func (buo *BouncerUpdateOne) ClearVersion() *BouncerUpdateOne {
 	buo.mutation.ClearVersion()
 	return buo
 }
 
-// SetUntil sets the until field.
+// SetUntil sets the "until" field.
 func (buo *BouncerUpdateOne) SetUntil(t time.Time) *BouncerUpdateOne {
 	buo.mutation.SetUntil(t)
 	return buo
 }
 
-// SetNillableUntil sets the until field if the given value is not nil.
+// SetNillableUntil sets the "until" field if the given value is not nil.
 func (buo *BouncerUpdateOne) SetNillableUntil(t *time.Time) *BouncerUpdateOne {
 	if t != nil {
 		buo.SetUntil(*t)
@@ -474,19 +473,19 @@ func (buo *BouncerUpdateOne) SetNillableUntil(t *time.Time) *BouncerUpdateOne {
 	return buo
 }
 
-// ClearUntil clears the value of until.
+// ClearUntil clears the value of the "until" field.
 func (buo *BouncerUpdateOne) ClearUntil() *BouncerUpdateOne {
 	buo.mutation.ClearUntil()
 	return buo
 }
 
-// SetLastPull sets the last_pull field.
+// SetLastPull sets the "last_pull" field.
 func (buo *BouncerUpdateOne) SetLastPull(t time.Time) *BouncerUpdateOne {
 	buo.mutation.SetLastPull(t)
 	return buo
 }
 
-// SetNillableLastPull sets the last_pull field if the given value is not nil.
+// SetNillableLastPull sets the "last_pull" field if the given value is not nil.
 func (buo *BouncerUpdateOne) SetNillableLastPull(t *time.Time) *BouncerUpdateOne {
 	if t != nil {
 		buo.SetLastPull(*t)
@@ -499,7 +498,7 @@ func (buo *BouncerUpdateOne) Mutation() *BouncerMutation {
 	return buo.mutation
 }
 
-// Save executes the query and returns the updated entity.
+// Save executes the query and returns the updated Bouncer entity.
 func (buo *BouncerUpdateOne) Save(ctx context.Context) (*Bouncer, error) {
 	var (
 		err  error
@@ -662,7 +661,7 @@ func (buo *BouncerUpdateOne) sqlSave(ctx context.Context) (_node *Bouncer, err e
 	}
 	_node = &Bouncer{config: buo.config}
 	_spec.Assign = _node.assignValues
-	_spec.ScanValues = _node.scanValues()
+	_spec.ScanValues = _node.scanValues
 	if err = sqlgraph.UpdateNode(ctx, buo.driver, _spec); err != nil {
 		if _, ok := err.(*sqlgraph.NotFoundError); ok {
 			err = &NotFoundError{bouncer.Label}

+ 7 - 7
pkg/database/ent/client.go

@@ -98,7 +98,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
 	}, nil
 }
 
-// BeginTx returns a transactional client with options.
+// BeginTx returns a transactional client with specified options.
 func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
 	if _, ok := c.driver.(*txDriver); ok {
 		return nil, fmt.Errorf("ent: cannot start a transaction within a transaction")
@@ -174,7 +174,7 @@ func (c *AlertClient) Create() *AlertCreate {
 	return &AlertCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
 }
 
-// BulkCreate returns a builder for creating a bulk of Alert entities.
+// CreateBulk returns a builder for creating a bulk of Alert entities.
 func (c *AlertClient) CreateBulk(builders ...*AlertCreate) *AlertCreateBulk {
 	return &AlertCreateBulk{config: c.config, builders: builders}
 }
@@ -326,7 +326,7 @@ func (c *BouncerClient) Create() *BouncerCreate {
 	return &BouncerCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
 }
 
-// BulkCreate returns a builder for creating a bulk of Bouncer entities.
+// CreateBulk returns a builder for creating a bulk of Bouncer entities.
 func (c *BouncerClient) CreateBulk(builders ...*BouncerCreate) *BouncerCreateBulk {
 	return &BouncerCreateBulk{config: c.config, builders: builders}
 }
@@ -414,7 +414,7 @@ func (c *DecisionClient) Create() *DecisionCreate {
 	return &DecisionCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
 }
 
-// BulkCreate returns a builder for creating a bulk of Decision entities.
+// CreateBulk returns a builder for creating a bulk of Decision entities.
 func (c *DecisionClient) CreateBulk(builders ...*DecisionCreate) *DecisionCreateBulk {
 	return &DecisionCreateBulk{config: c.config, builders: builders}
 }
@@ -518,7 +518,7 @@ func (c *EventClient) Create() *EventCreate {
 	return &EventCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
 }
 
-// BulkCreate returns a builder for creating a bulk of Event entities.
+// CreateBulk returns a builder for creating a bulk of Event entities.
 func (c *EventClient) CreateBulk(builders ...*EventCreate) *EventCreateBulk {
 	return &EventCreateBulk{config: c.config, builders: builders}
 }
@@ -622,7 +622,7 @@ func (c *MachineClient) Create() *MachineCreate {
 	return &MachineCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
 }
 
-// BulkCreate returns a builder for creating a bulk of Machine entities.
+// CreateBulk returns a builder for creating a bulk of Machine entities.
 func (c *MachineClient) CreateBulk(builders ...*MachineCreate) *MachineCreateBulk {
 	return &MachineCreateBulk{config: c.config, builders: builders}
 }
@@ -726,7 +726,7 @@ func (c *MetaClient) Create() *MetaCreate {
 	return &MetaCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
 }
 
-// BulkCreate returns a builder for creating a bulk of Meta entities.
+// CreateBulk returns a builder for creating a bulk of Meta entities.
 func (c *MetaClient) CreateBulk(builders ...*MetaCreate) *MetaCreateBulk {
 	return &MetaCreateBulk{config: c.config, builders: builders}
 }

+ 3 - 3
pkg/database/ent/context.go

@@ -8,7 +8,7 @@ import (
 
 type clientCtxKey struct{}
 
-// FromContext returns the Client stored in a context, or nil if there isn't one.
+// FromContext returns a Client stored inside a context, or nil if there isn't one.
 func FromContext(ctx context.Context) *Client {
 	c, _ := ctx.Value(clientCtxKey{}).(*Client)
 	return c
@@ -21,13 +21,13 @@ func NewContext(parent context.Context, c *Client) context.Context {
 
 type txCtxKey struct{}
 
-// TxFromContext returns the Tx stored in a context, or nil if there isn't one.
+// TxFromContext returns a Tx stored inside a context, or nil if there isn't one.
 func TxFromContext(ctx context.Context) *Tx {
 	tx, _ := ctx.Value(txCtxKey{}).(*Tx)
 	return tx
 }
 
-// NewTxContext returns a new context with the given Client attached.
+// NewTxContext returns a new context with the given Tx attached.
 func NewTxContext(parent context.Context, tx *Tx) context.Context {
 	return context.WithValue(parent, txCtxKey{}, tx)
 }

+ 135 - 95
pkg/database/ent/decision.go

@@ -31,6 +31,12 @@ type Decision struct {
 	StartIP int64 `json:"start_ip,omitempty"`
 	// EndIP holds the value of the "end_ip" field.
 	EndIP int64 `json:"end_ip,omitempty"`
+	// StartSuffix holds the value of the "start_suffix" field.
+	StartSuffix int64 `json:"start_suffix,omitempty"`
+	// EndSuffix holds the value of the "end_suffix" field.
+	EndSuffix int64 `json:"end_suffix,omitempty"`
+	// IPSize holds the value of the "ip_size" field.
+	IPSize int64 `json:"ip_size,omitempty"`
 	// Scope holds the value of the "scope" field.
 	Scope string `json:"scope,omitempty"`
 	// Value holds the value of the "value" field.
@@ -69,123 +75,151 @@ func (e DecisionEdges) OwnerOrErr() (*Alert, error) {
 }
 
 // scanValues returns the types for scanning values from sql.Rows.
-func (*Decision) scanValues() []interface{} {
-	return []interface{}{
-		&sql.NullInt64{},  // id
-		&sql.NullTime{},   // created_at
-		&sql.NullTime{},   // updated_at
-		&sql.NullTime{},   // until
-		&sql.NullString{}, // scenario
-		&sql.NullString{}, // type
-		&sql.NullInt64{},  // start_ip
-		&sql.NullInt64{},  // end_ip
-		&sql.NullString{}, // scope
-		&sql.NullString{}, // value
-		&sql.NullString{}, // origin
-		&sql.NullBool{},   // simulated
-	}
-}
-
-// fkValues returns the types for scanning foreign-keys values from sql.Rows.
-func (*Decision) fkValues() []interface{} {
-	return []interface{}{
-		&sql.NullInt64{}, // alert_decisions
+func (*Decision) scanValues(columns []string) ([]interface{}, error) {
+	values := make([]interface{}, len(columns))
+	for i := range columns {
+		switch columns[i] {
+		case decision.FieldSimulated:
+			values[i] = &sql.NullBool{}
+		case decision.FieldID, decision.FieldStartIP, decision.FieldEndIP, decision.FieldStartSuffix, decision.FieldEndSuffix, decision.FieldIPSize:
+			values[i] = &sql.NullInt64{}
+		case decision.FieldScenario, decision.FieldType, decision.FieldScope, decision.FieldValue, decision.FieldOrigin:
+			values[i] = &sql.NullString{}
+		case decision.FieldCreatedAt, decision.FieldUpdatedAt, decision.FieldUntil:
+			values[i] = &sql.NullTime{}
+		case decision.ForeignKeys[0]: // alert_decisions
+			values[i] = &sql.NullInt64{}
+		default:
+			return nil, fmt.Errorf("unexpected column %q for type Decision", columns[i])
+		}
 	}
+	return values, nil
 }
 
 // assignValues assigns the values that were returned from sql.Rows (after scanning)
 // to the Decision fields.
-func (d *Decision) assignValues(values ...interface{}) error {
-	if m, n := len(values), len(decision.Columns); m < n {
+func (d *Decision) assignValues(columns []string, values []interface{}) error {
+	if m, n := len(values), len(columns); m < n {
 		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
 	}
-	value, ok := values[0].(*sql.NullInt64)
-	if !ok {
-		return fmt.Errorf("unexpected type %T for field id", value)
-	}
-	d.ID = int(value.Int64)
-	values = values[1:]
-	if value, ok := values[0].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field created_at", values[0])
-	} else if value.Valid {
-		d.CreatedAt = value.Time
-	}
-	if value, ok := values[1].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field updated_at", values[1])
-	} else if value.Valid {
-		d.UpdatedAt = value.Time
-	}
-	if value, ok := values[2].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field until", values[2])
-	} else if value.Valid {
-		d.Until = value.Time
-	}
-	if value, ok := values[3].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field scenario", values[3])
-	} else if value.Valid {
-		d.Scenario = value.String
-	}
-	if value, ok := values[4].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field type", values[4])
-	} else if value.Valid {
-		d.Type = value.String
-	}
-	if value, ok := values[5].(*sql.NullInt64); !ok {
-		return fmt.Errorf("unexpected type %T for field start_ip", values[5])
-	} else if value.Valid {
-		d.StartIP = value.Int64
-	}
-	if value, ok := values[6].(*sql.NullInt64); !ok {
-		return fmt.Errorf("unexpected type %T for field end_ip", values[6])
-	} else if value.Valid {
-		d.EndIP = value.Int64
-	}
-	if value, ok := values[7].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field scope", values[7])
-	} else if value.Valid {
-		d.Scope = value.String
-	}
-	if value, ok := values[8].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field value", values[8])
-	} else if value.Valid {
-		d.Value = value.String
-	}
-	if value, ok := values[9].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field origin", values[9])
-	} else if value.Valid {
-		d.Origin = value.String
-	}
-	if value, ok := values[10].(*sql.NullBool); !ok {
-		return fmt.Errorf("unexpected type %T for field simulated", values[10])
-	} else if value.Valid {
-		d.Simulated = value.Bool
-	}
-	values = values[11:]
-	if len(values) == len(decision.ForeignKeys) {
-		if value, ok := values[0].(*sql.NullInt64); !ok {
-			return fmt.Errorf("unexpected type %T for edge-field alert_decisions", value)
-		} else if value.Valid {
-			d.alert_decisions = new(int)
-			*d.alert_decisions = int(value.Int64)
+	for i := range columns {
+		switch columns[i] {
+		case decision.FieldID:
+			value, ok := values[i].(*sql.NullInt64)
+			if !ok {
+				return fmt.Errorf("unexpected type %T for field id", value)
+			}
+			d.ID = int(value.Int64)
+		case decision.FieldCreatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field created_at", values[i])
+			} else if value.Valid {
+				d.CreatedAt = value.Time
+			}
+		case decision.FieldUpdatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field updated_at", values[i])
+			} else if value.Valid {
+				d.UpdatedAt = value.Time
+			}
+		case decision.FieldUntil:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field until", values[i])
+			} else if value.Valid {
+				d.Until = value.Time
+			}
+		case decision.FieldScenario:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field scenario", values[i])
+			} else if value.Valid {
+				d.Scenario = value.String
+			}
+		case decision.FieldType:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field type", values[i])
+			} else if value.Valid {
+				d.Type = value.String
+			}
+		case decision.FieldStartIP:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field start_ip", values[i])
+			} else if value.Valid {
+				d.StartIP = value.Int64
+			}
+		case decision.FieldEndIP:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field end_ip", values[i])
+			} else if value.Valid {
+				d.EndIP = value.Int64
+			}
+		case decision.FieldStartSuffix:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field start_suffix", values[i])
+			} else if value.Valid {
+				d.StartSuffix = value.Int64
+			}
+		case decision.FieldEndSuffix:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field end_suffix", values[i])
+			} else if value.Valid {
+				d.EndSuffix = value.Int64
+			}
+		case decision.FieldIPSize:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for field ip_size", values[i])
+			} else if value.Valid {
+				d.IPSize = value.Int64
+			}
+		case decision.FieldScope:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field scope", values[i])
+			} else if value.Valid {
+				d.Scope = value.String
+			}
+		case decision.FieldValue:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field value", values[i])
+			} else if value.Valid {
+				d.Value = value.String
+			}
+		case decision.FieldOrigin:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field origin", values[i])
+			} else if value.Valid {
+				d.Origin = value.String
+			}
+		case decision.FieldSimulated:
+			if value, ok := values[i].(*sql.NullBool); !ok {
+				return fmt.Errorf("unexpected type %T for field simulated", values[i])
+			} else if value.Valid {
+				d.Simulated = value.Bool
+			}
+		case decision.ForeignKeys[0]:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for edge-field alert_decisions", value)
+			} else if value.Valid {
+				d.alert_decisions = new(int)
+				*d.alert_decisions = int(value.Int64)
+			}
 		}
 	}
 	return nil
 }
 
-// QueryOwner queries the owner edge of the Decision.
+// QueryOwner queries the "owner" edge of the Decision entity.
 func (d *Decision) QueryOwner() *AlertQuery {
 	return (&DecisionClient{config: d.config}).QueryOwner(d)
 }
 
 // Update returns a builder for updating this Decision.
-// Note that, you need to call Decision.Unwrap() before calling this method, if this Decision
+// Note that you need to call Decision.Unwrap() before calling this method if this Decision
 // was returned from a transaction, and the transaction was committed or rolled back.
 func (d *Decision) Update() *DecisionUpdateOne {
 	return (&DecisionClient{config: d.config}).UpdateOne(d)
 }
 
-// Unwrap unwraps the entity that was returned from a transaction after it was closed,
-// so that all next queries will be executed through the driver which created the transaction.
+// Unwrap unwraps the Decision entity that was returned from a transaction after it was closed,
+// so that all future queries will be executed through the driver which created the transaction.
 func (d *Decision) Unwrap() *Decision {
 	tx, ok := d.config.driver.(*txDriver)
 	if !ok {
@@ -214,6 +248,12 @@ func (d *Decision) String() string {
 	builder.WriteString(fmt.Sprintf("%v", d.StartIP))
 	builder.WriteString(", end_ip=")
 	builder.WriteString(fmt.Sprintf("%v", d.EndIP))
+	builder.WriteString(", start_suffix=")
+	builder.WriteString(fmt.Sprintf("%v", d.StartSuffix))
+	builder.WriteString(", end_suffix=")
+	builder.WriteString(fmt.Sprintf("%v", d.EndSuffix))
+	builder.WriteString(", ip_size=")
+	builder.WriteString(fmt.Sprintf("%v", d.IPSize))
 	builder.WriteString(", scope=")
 	builder.WriteString(d.Scope)
 	builder.WriteString(", value=")

+ 12 - 3
pkg/database/ent/decision/decision.go

@@ -25,6 +25,12 @@ const (
 	FieldStartIP = "start_ip"
 	// FieldEndIP holds the string denoting the end_ip field in the database.
 	FieldEndIP = "end_ip"
+	// FieldStartSuffix holds the string denoting the start_suffix field in the database.
+	FieldStartSuffix = "start_suffix"
+	// FieldEndSuffix holds the string denoting the end_suffix field in the database.
+	FieldEndSuffix = "end_suffix"
+	// FieldIPSize holds the string denoting the ip_size field in the database.
+	FieldIPSize = "ip_size"
 	// FieldScope holds the string denoting the scope field in the database.
 	FieldScope = "scope"
 	// FieldValue holds the string denoting the value field in the database.
@@ -58,6 +64,9 @@ var Columns = []string{
 	FieldType,
 	FieldStartIP,
 	FieldEndIP,
+	FieldStartSuffix,
+	FieldEndSuffix,
+	FieldIPSize,
 	FieldScope,
 	FieldValue,
 	FieldOrigin,
@@ -85,10 +94,10 @@ func ValidColumn(column string) bool {
 }
 
 var (
-	// DefaultCreatedAt holds the default value on creation for the created_at field.
+	// DefaultCreatedAt holds the default value on creation for the "created_at" field.
 	DefaultCreatedAt func() time.Time
-	// DefaultUpdatedAt holds the default value on creation for the updated_at field.
+	// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
 	DefaultUpdatedAt func() time.Time
-	// DefaultSimulated holds the default value on creation for the simulated field.
+	// DefaultSimulated holds the default value on creation for the "simulated" field.
 	DefaultSimulated bool
 )

+ 294 - 3
pkg/database/ent/decision/where.go

@@ -10,7 +10,7 @@ import (
 	"github.com/facebook/ent/dialect/sql/sqlgraph"
 )
 
-// ID filters vertices based on their identifier.
+// ID filters vertices based on their ID field.
 func ID(id int) predicate.Decision {
 	return predicate.Decision(func(s *sql.Selector) {
 		s.Where(sql.EQ(s.C(FieldID), id))
@@ -142,6 +142,27 @@ func EndIP(v int64) predicate.Decision {
 	})
 }
 
+// StartSuffix applies equality check predicate on the "start_suffix" field. It's identical to StartSuffixEQ.
+func StartSuffix(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.EQ(s.C(FieldStartSuffix), v))
+	})
+}
+
+// EndSuffix applies equality check predicate on the "end_suffix" field. It's identical to EndSuffixEQ.
+func EndSuffix(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.EQ(s.C(FieldEndSuffix), v))
+	})
+}
+
+// IPSize applies equality check predicate on the "ip_size" field. It's identical to IPSizeEQ.
+func IPSize(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.EQ(s.C(FieldIPSize), v))
+	})
+}
+
 // Scope applies equality check predicate on the "scope" field. It's identical to ScopeEQ.
 func Scope(v string) predicate.Decision {
 	return predicate.Decision(func(s *sql.Selector) {
@@ -800,6 +821,276 @@ func EndIPNotNil() predicate.Decision {
 	})
 }
 
+// StartSuffixEQ applies the EQ predicate on the "start_suffix" field.
+func StartSuffixEQ(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.EQ(s.C(FieldStartSuffix), v))
+	})
+}
+
+// StartSuffixNEQ applies the NEQ predicate on the "start_suffix" field.
+func StartSuffixNEQ(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.NEQ(s.C(FieldStartSuffix), v))
+	})
+}
+
+// StartSuffixIn applies the In predicate on the "start_suffix" field.
+func StartSuffixIn(vs ...int64) predicate.Decision {
+	v := make([]interface{}, len(vs))
+	for i := range v {
+		v[i] = vs[i]
+	}
+	return predicate.Decision(func(s *sql.Selector) {
+		// if not arguments were provided, append the FALSE constants,
+		// since we can't apply "IN ()". This will make this predicate falsy.
+		if len(v) == 0 {
+			s.Where(sql.False())
+			return
+		}
+		s.Where(sql.In(s.C(FieldStartSuffix), v...))
+	})
+}
+
+// StartSuffixNotIn applies the NotIn predicate on the "start_suffix" field.
+func StartSuffixNotIn(vs ...int64) predicate.Decision {
+	v := make([]interface{}, len(vs))
+	for i := range v {
+		v[i] = vs[i]
+	}
+	return predicate.Decision(func(s *sql.Selector) {
+		// if not arguments were provided, append the FALSE constants,
+		// since we can't apply "IN ()". This will make this predicate falsy.
+		if len(v) == 0 {
+			s.Where(sql.False())
+			return
+		}
+		s.Where(sql.NotIn(s.C(FieldStartSuffix), v...))
+	})
+}
+
+// StartSuffixGT applies the GT predicate on the "start_suffix" field.
+func StartSuffixGT(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.GT(s.C(FieldStartSuffix), v))
+	})
+}
+
+// StartSuffixGTE applies the GTE predicate on the "start_suffix" field.
+func StartSuffixGTE(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.GTE(s.C(FieldStartSuffix), v))
+	})
+}
+
+// StartSuffixLT applies the LT predicate on the "start_suffix" field.
+func StartSuffixLT(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.LT(s.C(FieldStartSuffix), v))
+	})
+}
+
+// StartSuffixLTE applies the LTE predicate on the "start_suffix" field.
+func StartSuffixLTE(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.LTE(s.C(FieldStartSuffix), v))
+	})
+}
+
+// StartSuffixIsNil applies the IsNil predicate on the "start_suffix" field.
+func StartSuffixIsNil() predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.IsNull(s.C(FieldStartSuffix)))
+	})
+}
+
+// StartSuffixNotNil applies the NotNil predicate on the "start_suffix" field.
+func StartSuffixNotNil() predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.NotNull(s.C(FieldStartSuffix)))
+	})
+}
+
+// EndSuffixEQ applies the EQ predicate on the "end_suffix" field.
+func EndSuffixEQ(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.EQ(s.C(FieldEndSuffix), v))
+	})
+}
+
+// EndSuffixNEQ applies the NEQ predicate on the "end_suffix" field.
+func EndSuffixNEQ(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.NEQ(s.C(FieldEndSuffix), v))
+	})
+}
+
+// EndSuffixIn applies the In predicate on the "end_suffix" field.
+func EndSuffixIn(vs ...int64) predicate.Decision {
+	v := make([]interface{}, len(vs))
+	for i := range v {
+		v[i] = vs[i]
+	}
+	return predicate.Decision(func(s *sql.Selector) {
+		// if not arguments were provided, append the FALSE constants,
+		// since we can't apply "IN ()". This will make this predicate falsy.
+		if len(v) == 0 {
+			s.Where(sql.False())
+			return
+		}
+		s.Where(sql.In(s.C(FieldEndSuffix), v...))
+	})
+}
+
+// EndSuffixNotIn applies the NotIn predicate on the "end_suffix" field.
+func EndSuffixNotIn(vs ...int64) predicate.Decision {
+	v := make([]interface{}, len(vs))
+	for i := range v {
+		v[i] = vs[i]
+	}
+	return predicate.Decision(func(s *sql.Selector) {
+		// if not arguments were provided, append the FALSE constants,
+		// since we can't apply "IN ()". This will make this predicate falsy.
+		if len(v) == 0 {
+			s.Where(sql.False())
+			return
+		}
+		s.Where(sql.NotIn(s.C(FieldEndSuffix), v...))
+	})
+}
+
+// EndSuffixGT applies the GT predicate on the "end_suffix" field.
+func EndSuffixGT(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.GT(s.C(FieldEndSuffix), v))
+	})
+}
+
+// EndSuffixGTE applies the GTE predicate on the "end_suffix" field.
+func EndSuffixGTE(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.GTE(s.C(FieldEndSuffix), v))
+	})
+}
+
+// EndSuffixLT applies the LT predicate on the "end_suffix" field.
+func EndSuffixLT(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.LT(s.C(FieldEndSuffix), v))
+	})
+}
+
+// EndSuffixLTE applies the LTE predicate on the "end_suffix" field.
+func EndSuffixLTE(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.LTE(s.C(FieldEndSuffix), v))
+	})
+}
+
+// EndSuffixIsNil applies the IsNil predicate on the "end_suffix" field.
+func EndSuffixIsNil() predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.IsNull(s.C(FieldEndSuffix)))
+	})
+}
+
+// EndSuffixNotNil applies the NotNil predicate on the "end_suffix" field.
+func EndSuffixNotNil() predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.NotNull(s.C(FieldEndSuffix)))
+	})
+}
+
+// IPSizeEQ applies the EQ predicate on the "ip_size" field.
+func IPSizeEQ(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.EQ(s.C(FieldIPSize), v))
+	})
+}
+
+// IPSizeNEQ applies the NEQ predicate on the "ip_size" field.
+func IPSizeNEQ(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.NEQ(s.C(FieldIPSize), v))
+	})
+}
+
+// IPSizeIn applies the In predicate on the "ip_size" field.
+func IPSizeIn(vs ...int64) predicate.Decision {
+	v := make([]interface{}, len(vs))
+	for i := range v {
+		v[i] = vs[i]
+	}
+	return predicate.Decision(func(s *sql.Selector) {
+		// if not arguments were provided, append the FALSE constants,
+		// since we can't apply "IN ()". This will make this predicate falsy.
+		if len(v) == 0 {
+			s.Where(sql.False())
+			return
+		}
+		s.Where(sql.In(s.C(FieldIPSize), v...))
+	})
+}
+
+// IPSizeNotIn applies the NotIn predicate on the "ip_size" field.
+func IPSizeNotIn(vs ...int64) predicate.Decision {
+	v := make([]interface{}, len(vs))
+	for i := range v {
+		v[i] = vs[i]
+	}
+	return predicate.Decision(func(s *sql.Selector) {
+		// if not arguments were provided, append the FALSE constants,
+		// since we can't apply "IN ()". This will make this predicate falsy.
+		if len(v) == 0 {
+			s.Where(sql.False())
+			return
+		}
+		s.Where(sql.NotIn(s.C(FieldIPSize), v...))
+	})
+}
+
+// IPSizeGT applies the GT predicate on the "ip_size" field.
+func IPSizeGT(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.GT(s.C(FieldIPSize), v))
+	})
+}
+
+// IPSizeGTE applies the GTE predicate on the "ip_size" field.
+func IPSizeGTE(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.GTE(s.C(FieldIPSize), v))
+	})
+}
+
+// IPSizeLT applies the LT predicate on the "ip_size" field.
+func IPSizeLT(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.LT(s.C(FieldIPSize), v))
+	})
+}
+
+// IPSizeLTE applies the LTE predicate on the "ip_size" field.
+func IPSizeLTE(v int64) predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.LTE(s.C(FieldIPSize), v))
+	})
+}
+
+// IPSizeIsNil applies the IsNil predicate on the "ip_size" field.
+func IPSizeIsNil() predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.IsNull(s.C(FieldIPSize)))
+	})
+}
+
+// IPSizeNotNil applies the NotNil predicate on the "ip_size" field.
+func IPSizeNotNil() predicate.Decision {
+	return predicate.Decision(func(s *sql.Selector) {
+		s.Where(sql.NotNull(s.C(FieldIPSize)))
+	})
+}
+
 // ScopeEQ applies the EQ predicate on the "scope" field.
 func ScopeEQ(v string) predicate.Decision {
 	return predicate.Decision(func(s *sql.Selector) {
@@ -1175,7 +1466,7 @@ func HasOwnerWith(preds ...predicate.Alert) predicate.Decision {
 	})
 }
 
-// And groups list of predicates with the AND operator between them.
+// And groups predicates with the AND operator between them.
 func And(predicates ...predicate.Decision) predicate.Decision {
 	return predicate.Decision(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)
@@ -1186,7 +1477,7 @@ func And(predicates ...predicate.Decision) predicate.Decision {
 	})
 }
 
-// Or groups list of predicates with the OR operator between them.
+// Or groups predicates with the OR operator between them.
 func Or(predicates ...predicate.Decision) predicate.Decision {
 	return predicate.Decision(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)

+ 87 - 21
pkg/database/ent/decision_create.go

@@ -21,13 +21,13 @@ type DecisionCreate struct {
 	hooks    []Hook
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (dc *DecisionCreate) SetCreatedAt(t time.Time) *DecisionCreate {
 	dc.mutation.SetCreatedAt(t)
 	return dc
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (dc *DecisionCreate) SetNillableCreatedAt(t *time.Time) *DecisionCreate {
 	if t != nil {
 		dc.SetCreatedAt(*t)
@@ -35,13 +35,13 @@ func (dc *DecisionCreate) SetNillableCreatedAt(t *time.Time) *DecisionCreate {
 	return dc
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (dc *DecisionCreate) SetUpdatedAt(t time.Time) *DecisionCreate {
 	dc.mutation.SetUpdatedAt(t)
 	return dc
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (dc *DecisionCreate) SetNillableUpdatedAt(t *time.Time) *DecisionCreate {
 	if t != nil {
 		dc.SetUpdatedAt(*t)
@@ -49,31 +49,31 @@ func (dc *DecisionCreate) SetNillableUpdatedAt(t *time.Time) *DecisionCreate {
 	return dc
 }
 
-// SetUntil sets the until field.
+// SetUntil sets the "until" field.
 func (dc *DecisionCreate) SetUntil(t time.Time) *DecisionCreate {
 	dc.mutation.SetUntil(t)
 	return dc
 }
 
-// SetScenario sets the scenario field.
+// SetScenario sets the "scenario" field.
 func (dc *DecisionCreate) SetScenario(s string) *DecisionCreate {
 	dc.mutation.SetScenario(s)
 	return dc
 }
 
-// SetType sets the type field.
+// SetType sets the "type" field.
 func (dc *DecisionCreate) SetType(s string) *DecisionCreate {
 	dc.mutation.SetType(s)
 	return dc
 }
 
-// SetStartIP sets the start_ip field.
+// SetStartIP sets the "start_ip" field.
 func (dc *DecisionCreate) SetStartIP(i int64) *DecisionCreate {
 	dc.mutation.SetStartIP(i)
 	return dc
 }
 
-// SetNillableStartIP sets the start_ip field if the given value is not nil.
+// SetNillableStartIP sets the "start_ip" field if the given value is not nil.
 func (dc *DecisionCreate) SetNillableStartIP(i *int64) *DecisionCreate {
 	if i != nil {
 		dc.SetStartIP(*i)
@@ -81,13 +81,13 @@ func (dc *DecisionCreate) SetNillableStartIP(i *int64) *DecisionCreate {
 	return dc
 }
 
-// SetEndIP sets the end_ip field.
+// SetEndIP sets the "end_ip" field.
 func (dc *DecisionCreate) SetEndIP(i int64) *DecisionCreate {
 	dc.mutation.SetEndIP(i)
 	return dc
 }
 
-// SetNillableEndIP sets the end_ip field if the given value is not nil.
+// SetNillableEndIP sets the "end_ip" field if the given value is not nil.
 func (dc *DecisionCreate) SetNillableEndIP(i *int64) *DecisionCreate {
 	if i != nil {
 		dc.SetEndIP(*i)
@@ -95,31 +95,73 @@ func (dc *DecisionCreate) SetNillableEndIP(i *int64) *DecisionCreate {
 	return dc
 }
 
-// SetScope sets the scope field.
+// SetStartSuffix sets the "start_suffix" field.
+func (dc *DecisionCreate) SetStartSuffix(i int64) *DecisionCreate {
+	dc.mutation.SetStartSuffix(i)
+	return dc
+}
+
+// SetNillableStartSuffix sets the "start_suffix" field if the given value is not nil.
+func (dc *DecisionCreate) SetNillableStartSuffix(i *int64) *DecisionCreate {
+	if i != nil {
+		dc.SetStartSuffix(*i)
+	}
+	return dc
+}
+
+// SetEndSuffix sets the "end_suffix" field.
+func (dc *DecisionCreate) SetEndSuffix(i int64) *DecisionCreate {
+	dc.mutation.SetEndSuffix(i)
+	return dc
+}
+
+// SetNillableEndSuffix sets the "end_suffix" field if the given value is not nil.
+func (dc *DecisionCreate) SetNillableEndSuffix(i *int64) *DecisionCreate {
+	if i != nil {
+		dc.SetEndSuffix(*i)
+	}
+	return dc
+}
+
+// SetIPSize sets the "ip_size" field.
+func (dc *DecisionCreate) SetIPSize(i int64) *DecisionCreate {
+	dc.mutation.SetIPSize(i)
+	return dc
+}
+
+// SetNillableIPSize sets the "ip_size" field if the given value is not nil.
+func (dc *DecisionCreate) SetNillableIPSize(i *int64) *DecisionCreate {
+	if i != nil {
+		dc.SetIPSize(*i)
+	}
+	return dc
+}
+
+// SetScope sets the "scope" field.
 func (dc *DecisionCreate) SetScope(s string) *DecisionCreate {
 	dc.mutation.SetScope(s)
 	return dc
 }
 
-// SetValue sets the value field.
+// SetValue sets the "value" field.
 func (dc *DecisionCreate) SetValue(s string) *DecisionCreate {
 	dc.mutation.SetValue(s)
 	return dc
 }
 
-// SetOrigin sets the origin field.
+// SetOrigin sets the "origin" field.
 func (dc *DecisionCreate) SetOrigin(s string) *DecisionCreate {
 	dc.mutation.SetOrigin(s)
 	return dc
 }
 
-// SetSimulated sets the simulated field.
+// SetSimulated sets the "simulated" field.
 func (dc *DecisionCreate) SetSimulated(b bool) *DecisionCreate {
 	dc.mutation.SetSimulated(b)
 	return dc
 }
 
-// SetNillableSimulated sets the simulated field if the given value is not nil.
+// SetNillableSimulated sets the "simulated" field if the given value is not nil.
 func (dc *DecisionCreate) SetNillableSimulated(b *bool) *DecisionCreate {
 	if b != nil {
 		dc.SetSimulated(*b)
@@ -127,13 +169,13 @@ func (dc *DecisionCreate) SetNillableSimulated(b *bool) *DecisionCreate {
 	return dc
 }
 
-// SetOwnerID sets the owner edge to Alert by id.
+// SetOwnerID sets the "owner" edge to the Alert entity by ID.
 func (dc *DecisionCreate) SetOwnerID(id int) *DecisionCreate {
 	dc.mutation.SetOwnerID(id)
 	return dc
 }
 
-// SetNillableOwnerID sets the owner edge to Alert by id if the given value is not nil.
+// SetNillableOwnerID sets the "owner" edge to the Alert entity by ID if the given value is not nil.
 func (dc *DecisionCreate) SetNillableOwnerID(id *int) *DecisionCreate {
 	if id != nil {
 		dc = dc.SetOwnerID(*id)
@@ -141,7 +183,7 @@ func (dc *DecisionCreate) SetNillableOwnerID(id *int) *DecisionCreate {
 	return dc
 }
 
-// SetOwner sets the owner edge to Alert.
+// SetOwner sets the "owner" edge to the Alert entity.
 func (dc *DecisionCreate) SetOwner(a *Alert) *DecisionCreate {
 	return dc.SetOwnerID(a.ID)
 }
@@ -324,6 +366,30 @@ func (dc *DecisionCreate) createSpec() (*Decision, *sqlgraph.CreateSpec) {
 		})
 		_node.EndIP = value
 	}
+	if value, ok := dc.mutation.StartSuffix(); ok {
+		_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldStartSuffix,
+		})
+		_node.StartSuffix = value
+	}
+	if value, ok := dc.mutation.EndSuffix(); ok {
+		_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldEndSuffix,
+		})
+		_node.EndSuffix = value
+	}
+	if value, ok := dc.mutation.IPSize(); ok {
+		_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldIPSize,
+		})
+		_node.IPSize = value
+	}
 	if value, ok := dc.mutation.Scope(); ok {
 		_spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{
 			Type:   field.TypeString,
@@ -378,7 +444,7 @@ func (dc *DecisionCreate) createSpec() (*Decision, *sqlgraph.CreateSpec) {
 	return _node, _spec
 }
 
-// DecisionCreateBulk is the builder for creating a bulk of Decision entities.
+// DecisionCreateBulk is the builder for creating many Decision entities in bulk.
 type DecisionCreateBulk struct {
 	config
 	builders []*DecisionCreate
@@ -436,7 +502,7 @@ func (dcb *DecisionCreateBulk) Save(ctx context.Context) ([]*Decision, error) {
 	return nodes, nil
 }
 
-// SaveX calls Save and panics if Save returns an error.
+// SaveX is like Save, but panics if an error occurs.
 func (dcb *DecisionCreateBulk) SaveX(ctx context.Context) []*Decision {
 	v, err := dcb.Save(ctx)
 	if err != nil {

+ 5 - 6
pkg/database/ent/decision_delete.go

@@ -16,14 +16,13 @@ import (
 // DecisionDelete is the builder for deleting a Decision entity.
 type DecisionDelete struct {
 	config
-	hooks      []Hook
-	mutation   *DecisionMutation
-	predicates []predicate.Decision
+	hooks    []Hook
+	mutation *DecisionMutation
 }
 
-// Where adds a new predicate to the delete builder.
+// Where adds a new predicate to the DecisionDelete builder.
 func (dd *DecisionDelete) Where(ps ...predicate.Decision) *DecisionDelete {
-	dd.predicates = append(dd.predicates, ps...)
+	dd.mutation.predicates = append(dd.mutation.predicates, ps...)
 	return dd
 }
 
@@ -75,7 +74,7 @@ func (dd *DecisionDelete) sqlExec(ctx context.Context) (int, error) {
 			},
 		},
 	}
-	if ps := dd.predicates; len(ps) > 0 {
+	if ps := dd.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)

+ 78 - 65
pkg/database/ent/decision_query.go

@@ -22,7 +22,7 @@ type DecisionQuery struct {
 	limit      *int
 	offset     *int
 	order      []OrderFunc
-	unique     []string
+	fields     []string
 	predicates []predicate.Decision
 	// eager-loading edges.
 	withOwner *AlertQuery
@@ -32,7 +32,7 @@ type DecisionQuery struct {
 	path func(context.Context) (*sql.Selector, error)
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the DecisionQuery builder.
 func (dq *DecisionQuery) Where(ps ...predicate.Decision) *DecisionQuery {
 	dq.predicates = append(dq.predicates, ps...)
 	return dq
@@ -56,7 +56,7 @@ func (dq *DecisionQuery) Order(o ...OrderFunc) *DecisionQuery {
 	return dq
 }
 
-// QueryOwner chains the current query on the owner edge.
+// QueryOwner chains the current query on the "owner" edge.
 func (dq *DecisionQuery) QueryOwner() *AlertQuery {
 	query := &AlertQuery{config: dq.config}
 	query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
@@ -78,7 +78,8 @@ func (dq *DecisionQuery) QueryOwner() *AlertQuery {
 	return query
 }
 
-// First returns the first Decision entity in the query. Returns *NotFoundError when no decision was found.
+// First returns the first Decision entity from the query.
+// Returns a *NotFoundError when no Decision was found.
 func (dq *DecisionQuery) First(ctx context.Context) (*Decision, error) {
 	nodes, err := dq.Limit(1).All(ctx)
 	if err != nil {
@@ -99,7 +100,8 @@ func (dq *DecisionQuery) FirstX(ctx context.Context) *Decision {
 	return node
 }
 
-// FirstID returns the first Decision id in the query. Returns *NotFoundError when no id was found.
+// FirstID returns the first Decision ID from the query.
+// Returns a *NotFoundError when no Decision ID was found.
 func (dq *DecisionQuery) FirstID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = dq.Limit(1).IDs(ctx); err != nil {
@@ -112,8 +114,8 @@ func (dq *DecisionQuery) FirstID(ctx context.Context) (id int, err error) {
 	return ids[0], nil
 }
 
-// FirstXID is like FirstID, but panics if an error occurs.
-func (dq *DecisionQuery) FirstXID(ctx context.Context) int {
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (dq *DecisionQuery) FirstIDX(ctx context.Context) int {
 	id, err := dq.FirstID(ctx)
 	if err != nil && !IsNotFound(err) {
 		panic(err)
@@ -121,7 +123,9 @@ func (dq *DecisionQuery) FirstXID(ctx context.Context) int {
 	return id
 }
 
-// Only returns the only Decision entity in the query, returns an error if not exactly one entity was returned.
+// Only returns a single Decision entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when exactly one Decision entity is not found.
+// Returns a *NotFoundError when no Decision entities are found.
 func (dq *DecisionQuery) Only(ctx context.Context) (*Decision, error) {
 	nodes, err := dq.Limit(2).All(ctx)
 	if err != nil {
@@ -146,7 +150,9 @@ func (dq *DecisionQuery) OnlyX(ctx context.Context) *Decision {
 	return node
 }
 
-// OnlyID returns the only Decision id in the query, returns an error if not exactly one id was returned.
+// OnlyID is like Only, but returns the only Decision ID in the query.
+// Returns a *NotSingularError when exactly one Decision ID is not found.
+// Returns a *NotFoundError when no entities are found.
 func (dq *DecisionQuery) OnlyID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = dq.Limit(2).IDs(ctx); err != nil {
@@ -189,7 +195,7 @@ func (dq *DecisionQuery) AllX(ctx context.Context) []*Decision {
 	return nodes
 }
 
-// IDs executes the query and returns a list of Decision ids.
+// IDs executes the query and returns a list of Decision IDs.
 func (dq *DecisionQuery) IDs(ctx context.Context) ([]int, error) {
 	var ids []int
 	if err := dq.Select(decision.FieldID).Scan(ctx, &ids); err != nil {
@@ -241,24 +247,27 @@ func (dq *DecisionQuery) ExistX(ctx context.Context) bool {
 	return exist
 }
 
-// Clone returns a duplicate of the query builder, including all associated steps. It can be
+// Clone returns a duplicate of the DecisionQuery builder, including all associated steps. It can be
 // used to prepare common query builders and use them differently after the clone is made.
 func (dq *DecisionQuery) Clone() *DecisionQuery {
+	if dq == nil {
+		return nil
+	}
 	return &DecisionQuery{
 		config:     dq.config,
 		limit:      dq.limit,
 		offset:     dq.offset,
 		order:      append([]OrderFunc{}, dq.order...),
-		unique:     append([]string{}, dq.unique...),
 		predicates: append([]predicate.Decision{}, dq.predicates...),
+		withOwner:  dq.withOwner.Clone(),
 		// clone intermediate query.
 		sql:  dq.sql.Clone(),
 		path: dq.path,
 	}
 }
 
-//  WithOwner tells the query-builder to eager-loads the nodes that are connected to
-// the "owner" edge. The optional arguments used to configure the query builder of the edge.
+// WithOwner tells the query-builder to eager-load the nodes that are connected to
+// the "owner" edge. The optional arguments are used to configure the query builder of the edge.
 func (dq *DecisionQuery) WithOwner(opts ...func(*AlertQuery)) *DecisionQuery {
 	query := &AlertQuery{config: dq.config}
 	for _, opt := range opts {
@@ -268,7 +277,7 @@ func (dq *DecisionQuery) WithOwner(opts ...func(*AlertQuery)) *DecisionQuery {
 	return dq
 }
 
-// GroupBy used to group vertices by one or more fields/columns.
+// GroupBy is used to group vertices by one or more fields/columns.
 // It is often used with aggregate functions, like: count, max, mean, min, sum.
 //
 // Example:
@@ -295,7 +304,8 @@ func (dq *DecisionQuery) GroupBy(field string, fields ...string) *DecisionGroupB
 	return group
 }
 
-// Select one or more fields from the given query.
+// Select allows the selection one or more fields/columns for the given query,
+// instead of selecting all fields in the entity.
 //
 // Example:
 //
@@ -308,18 +318,16 @@ func (dq *DecisionQuery) GroupBy(field string, fields ...string) *DecisionGroupB
 //		Scan(ctx, &v)
 //
 func (dq *DecisionQuery) Select(field string, fields ...string) *DecisionSelect {
-	selector := &DecisionSelect{config: dq.config}
-	selector.fields = append([]string{field}, fields...)
-	selector.path = func(ctx context.Context) (prev *sql.Selector, err error) {
-		if err := dq.prepareQuery(ctx); err != nil {
-			return nil, err
-		}
-		return dq.sqlQuery(), nil
-	}
-	return selector
+	dq.fields = append([]string{field}, fields...)
+	return &DecisionSelect{DecisionQuery: dq}
 }
 
 func (dq *DecisionQuery) prepareQuery(ctx context.Context) error {
+	for _, f := range dq.fields {
+		if !decision.ValidColumn(f) {
+			return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+		}
+	}
 	if dq.path != nil {
 		prev, err := dq.path(ctx)
 		if err != nil {
@@ -345,22 +353,18 @@ func (dq *DecisionQuery) sqlAll(ctx context.Context) ([]*Decision, error) {
 	if withFKs {
 		_spec.Node.Columns = append(_spec.Node.Columns, decision.ForeignKeys...)
 	}
-	_spec.ScanValues = func() []interface{} {
+	_spec.ScanValues = func(columns []string) ([]interface{}, error) {
 		node := &Decision{config: dq.config}
 		nodes = append(nodes, node)
-		values := node.scanValues()
-		if withFKs {
-			values = append(values, node.fkValues()...)
-		}
-		return values
+		return node.scanValues(columns)
 	}
-	_spec.Assign = func(values ...interface{}) error {
+	_spec.Assign = func(columns []string, values []interface{}) error {
 		if len(nodes) == 0 {
 			return fmt.Errorf("ent: Assign called without calling ScanValues")
 		}
 		node := nodes[len(nodes)-1]
 		node.Edges.loadedTypes = loadedTypes
-		return node.assignValues(values...)
+		return node.assignValues(columns, values)
 	}
 	if err := sqlgraph.QueryNodes(ctx, dq.driver, _spec); err != nil {
 		return nil, err
@@ -423,6 +427,15 @@ func (dq *DecisionQuery) querySpec() *sqlgraph.QuerySpec {
 		From:   dq.sql,
 		Unique: true,
 	}
+	if fields := dq.fields; len(fields) > 0 {
+		_spec.Node.Columns = make([]string, 0, len(fields))
+		_spec.Node.Columns = append(_spec.Node.Columns, decision.FieldID)
+		for i := range fields {
+			if fields[i] != decision.FieldID {
+				_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+			}
+		}
+	}
 	if ps := dq.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
@@ -471,7 +484,7 @@ func (dq *DecisionQuery) sqlQuery() *sql.Selector {
 	return selector
 }
 
-// DecisionGroupBy is the builder for group-by Decision entities.
+// DecisionGroupBy is the group-by builder for Decision entities.
 type DecisionGroupBy struct {
 	config
 	fields []string
@@ -487,7 +500,7 @@ func (dgb *DecisionGroupBy) Aggregate(fns ...AggregateFunc) *DecisionGroupBy {
 	return dgb
 }
 
-// Scan applies the group-by query and scan the result into the given value.
+// Scan applies the group-by query and scans the result into the given value.
 func (dgb *DecisionGroupBy) Scan(ctx context.Context, v interface{}) error {
 	query, err := dgb.path(ctx)
 	if err != nil {
@@ -504,7 +517,8 @@ func (dgb *DecisionGroupBy) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from group-by. It is only allowed when querying group-by with one field.
+// Strings returns list of strings from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (dgb *DecisionGroupBy) Strings(ctx context.Context) ([]string, error) {
 	if len(dgb.fields) > 1 {
 		return nil, errors.New("ent: DecisionGroupBy.Strings is not achievable when grouping more than 1 field")
@@ -525,7 +539,8 @@ func (dgb *DecisionGroupBy) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from group-by. It is only allowed when querying group-by with one field.
+// String returns a single string from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (dgb *DecisionGroupBy) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = dgb.Strings(ctx); err != nil {
@@ -551,7 +566,8 @@ func (dgb *DecisionGroupBy) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from group-by. It is only allowed when querying group-by with one field.
+// Ints returns list of ints from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (dgb *DecisionGroupBy) Ints(ctx context.Context) ([]int, error) {
 	if len(dgb.fields) > 1 {
 		return nil, errors.New("ent: DecisionGroupBy.Ints is not achievable when grouping more than 1 field")
@@ -572,7 +588,8 @@ func (dgb *DecisionGroupBy) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from group-by. It is only allowed when querying group-by with one field.
+// Int returns a single int from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (dgb *DecisionGroupBy) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = dgb.Ints(ctx); err != nil {
@@ -598,7 +615,8 @@ func (dgb *DecisionGroupBy) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from group-by. It is only allowed when querying group-by with one field.
+// Float64s returns list of float64s from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (dgb *DecisionGroupBy) Float64s(ctx context.Context) ([]float64, error) {
 	if len(dgb.fields) > 1 {
 		return nil, errors.New("ent: DecisionGroupBy.Float64s is not achievable when grouping more than 1 field")
@@ -619,7 +637,8 @@ func (dgb *DecisionGroupBy) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from group-by. It is only allowed when querying group-by with one field.
+// Float64 returns a single float64 from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (dgb *DecisionGroupBy) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = dgb.Float64s(ctx); err != nil {
@@ -645,7 +664,8 @@ func (dgb *DecisionGroupBy) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from group-by. It is only allowed when querying group-by with one field.
+// Bools returns list of bools from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (dgb *DecisionGroupBy) Bools(ctx context.Context) ([]bool, error) {
 	if len(dgb.fields) > 1 {
 		return nil, errors.New("ent: DecisionGroupBy.Bools is not achievable when grouping more than 1 field")
@@ -666,7 +686,8 @@ func (dgb *DecisionGroupBy) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from group-by. It is only allowed when querying group-by with one field.
+// Bool returns a single bool from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (dgb *DecisionGroupBy) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = dgb.Bools(ctx); err != nil {
@@ -721,22 +742,19 @@ func (dgb *DecisionGroupBy) sqlQuery() *sql.Selector {
 	return selector.Select(columns...).GroupBy(dgb.fields...)
 }
 
-// DecisionSelect is the builder for select fields of Decision entities.
+// DecisionSelect is the builder for selecting fields of Decision entities.
 type DecisionSelect struct {
-	config
-	fields []string
+	*DecisionQuery
 	// intermediate query (i.e. traversal path).
-	sql  *sql.Selector
-	path func(context.Context) (*sql.Selector, error)
+	sql *sql.Selector
 }
 
-// Scan applies the selector query and scan the result into the given value.
+// Scan applies the selector query and scans the result into the given value.
 func (ds *DecisionSelect) Scan(ctx context.Context, v interface{}) error {
-	query, err := ds.path(ctx)
-	if err != nil {
+	if err := ds.prepareQuery(ctx); err != nil {
 		return err
 	}
-	ds.sql = query
+	ds.sql = ds.DecisionQuery.sqlQuery()
 	return ds.sqlScan(ctx, v)
 }
 
@@ -747,7 +765,7 @@ func (ds *DecisionSelect) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from selector. It is only allowed when selecting one field.
+// Strings returns list of strings from a selector. It is only allowed when selecting one field.
 func (ds *DecisionSelect) Strings(ctx context.Context) ([]string, error) {
 	if len(ds.fields) > 1 {
 		return nil, errors.New("ent: DecisionSelect.Strings is not achievable when selecting more than 1 field")
@@ -768,7 +786,7 @@ func (ds *DecisionSelect) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from selector. It is only allowed when selecting one field.
+// String returns a single string from a selector. It is only allowed when selecting one field.
 func (ds *DecisionSelect) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = ds.Strings(ctx); err != nil {
@@ -794,7 +812,7 @@ func (ds *DecisionSelect) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from selector. It is only allowed when selecting one field.
+// Ints returns list of ints from a selector. It is only allowed when selecting one field.
 func (ds *DecisionSelect) Ints(ctx context.Context) ([]int, error) {
 	if len(ds.fields) > 1 {
 		return nil, errors.New("ent: DecisionSelect.Ints is not achievable when selecting more than 1 field")
@@ -815,7 +833,7 @@ func (ds *DecisionSelect) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from selector. It is only allowed when selecting one field.
+// Int returns a single int from a selector. It is only allowed when selecting one field.
 func (ds *DecisionSelect) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = ds.Ints(ctx); err != nil {
@@ -841,7 +859,7 @@ func (ds *DecisionSelect) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from selector. It is only allowed when selecting one field.
+// Float64s returns list of float64s from a selector. It is only allowed when selecting one field.
 func (ds *DecisionSelect) Float64s(ctx context.Context) ([]float64, error) {
 	if len(ds.fields) > 1 {
 		return nil, errors.New("ent: DecisionSelect.Float64s is not achievable when selecting more than 1 field")
@@ -862,7 +880,7 @@ func (ds *DecisionSelect) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from selector. It is only allowed when selecting one field.
+// Float64 returns a single float64 from a selector. It is only allowed when selecting one field.
 func (ds *DecisionSelect) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = ds.Float64s(ctx); err != nil {
@@ -888,7 +906,7 @@ func (ds *DecisionSelect) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from selector. It is only allowed when selecting one field.
+// Bools returns list of bools from a selector. It is only allowed when selecting one field.
 func (ds *DecisionSelect) Bools(ctx context.Context) ([]bool, error) {
 	if len(ds.fields) > 1 {
 		return nil, errors.New("ent: DecisionSelect.Bools is not achievable when selecting more than 1 field")
@@ -909,7 +927,7 @@ func (ds *DecisionSelect) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from selector. It is only allowed when selecting one field.
+// Bool returns a single bool from a selector. It is only allowed when selecting one field.
 func (ds *DecisionSelect) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = ds.Bools(ctx); err != nil {
@@ -936,11 +954,6 @@ func (ds *DecisionSelect) BoolX(ctx context.Context) bool {
 }
 
 func (ds *DecisionSelect) sqlScan(ctx context.Context, v interface{}) error {
-	for _, f := range ds.fields {
-		if !decision.ValidColumn(f) {
-			return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for selection", f)}
-		}
-	}
 	rows := &sql.Rows{}
 	query, args := ds.sqlQuery().Query()
 	if err := ds.driver.Query(ctx, query, args, rows); err != nil {

+ 338 - 57
pkg/database/ent/decision_update.go

@@ -18,24 +18,23 @@ import (
 // DecisionUpdate is the builder for updating Decision entities.
 type DecisionUpdate struct {
 	config
-	hooks      []Hook
-	mutation   *DecisionMutation
-	predicates []predicate.Decision
+	hooks    []Hook
+	mutation *DecisionMutation
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the DecisionUpdate builder.
 func (du *DecisionUpdate) Where(ps ...predicate.Decision) *DecisionUpdate {
-	du.predicates = append(du.predicates, ps...)
+	du.mutation.predicates = append(du.mutation.predicates, ps...)
 	return du
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (du *DecisionUpdate) SetCreatedAt(t time.Time) *DecisionUpdate {
 	du.mutation.SetCreatedAt(t)
 	return du
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (du *DecisionUpdate) SetNillableCreatedAt(t *time.Time) *DecisionUpdate {
 	if t != nil {
 		du.SetCreatedAt(*t)
@@ -43,13 +42,13 @@ func (du *DecisionUpdate) SetNillableCreatedAt(t *time.Time) *DecisionUpdate {
 	return du
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (du *DecisionUpdate) SetUpdatedAt(t time.Time) *DecisionUpdate {
 	du.mutation.SetUpdatedAt(t)
 	return du
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (du *DecisionUpdate) SetNillableUpdatedAt(t *time.Time) *DecisionUpdate {
 	if t != nil {
 		du.SetUpdatedAt(*t)
@@ -57,32 +56,32 @@ func (du *DecisionUpdate) SetNillableUpdatedAt(t *time.Time) *DecisionUpdate {
 	return du
 }
 
-// SetUntil sets the until field.
+// SetUntil sets the "until" field.
 func (du *DecisionUpdate) SetUntil(t time.Time) *DecisionUpdate {
 	du.mutation.SetUntil(t)
 	return du
 }
 
-// SetScenario sets the scenario field.
+// SetScenario sets the "scenario" field.
 func (du *DecisionUpdate) SetScenario(s string) *DecisionUpdate {
 	du.mutation.SetScenario(s)
 	return du
 }
 
-// SetType sets the type field.
+// SetType sets the "type" field.
 func (du *DecisionUpdate) SetType(s string) *DecisionUpdate {
 	du.mutation.SetType(s)
 	return du
 }
 
-// SetStartIP sets the start_ip field.
+// SetStartIP sets the "start_ip" field.
 func (du *DecisionUpdate) SetStartIP(i int64) *DecisionUpdate {
 	du.mutation.ResetStartIP()
 	du.mutation.SetStartIP(i)
 	return du
 }
 
-// SetNillableStartIP sets the start_ip field if the given value is not nil.
+// SetNillableStartIP sets the "start_ip" field if the given value is not nil.
 func (du *DecisionUpdate) SetNillableStartIP(i *int64) *DecisionUpdate {
 	if i != nil {
 		du.SetStartIP(*i)
@@ -90,26 +89,26 @@ func (du *DecisionUpdate) SetNillableStartIP(i *int64) *DecisionUpdate {
 	return du
 }
 
-// AddStartIP adds i to start_ip.
+// AddStartIP adds i to the "start_ip" field.
 func (du *DecisionUpdate) AddStartIP(i int64) *DecisionUpdate {
 	du.mutation.AddStartIP(i)
 	return du
 }
 
-// ClearStartIP clears the value of start_ip.
+// ClearStartIP clears the value of the "start_ip" field.
 func (du *DecisionUpdate) ClearStartIP() *DecisionUpdate {
 	du.mutation.ClearStartIP()
 	return du
 }
 
-// SetEndIP sets the end_ip field.
+// SetEndIP sets the "end_ip" field.
 func (du *DecisionUpdate) SetEndIP(i int64) *DecisionUpdate {
 	du.mutation.ResetEndIP()
 	du.mutation.SetEndIP(i)
 	return du
 }
 
-// SetNillableEndIP sets the end_ip field if the given value is not nil.
+// SetNillableEndIP sets the "end_ip" field if the given value is not nil.
 func (du *DecisionUpdate) SetNillableEndIP(i *int64) *DecisionUpdate {
 	if i != nil {
 		du.SetEndIP(*i)
@@ -117,43 +116,124 @@ func (du *DecisionUpdate) SetNillableEndIP(i *int64) *DecisionUpdate {
 	return du
 }
 
-// AddEndIP adds i to end_ip.
+// AddEndIP adds i to the "end_ip" field.
 func (du *DecisionUpdate) AddEndIP(i int64) *DecisionUpdate {
 	du.mutation.AddEndIP(i)
 	return du
 }
 
-// ClearEndIP clears the value of end_ip.
+// ClearEndIP clears the value of the "end_ip" field.
 func (du *DecisionUpdate) ClearEndIP() *DecisionUpdate {
 	du.mutation.ClearEndIP()
 	return du
 }
 
-// SetScope sets the scope field.
+// SetStartSuffix sets the "start_suffix" field.
+func (du *DecisionUpdate) SetStartSuffix(i int64) *DecisionUpdate {
+	du.mutation.ResetStartSuffix()
+	du.mutation.SetStartSuffix(i)
+	return du
+}
+
+// SetNillableStartSuffix sets the "start_suffix" field if the given value is not nil.
+func (du *DecisionUpdate) SetNillableStartSuffix(i *int64) *DecisionUpdate {
+	if i != nil {
+		du.SetStartSuffix(*i)
+	}
+	return du
+}
+
+// AddStartSuffix adds i to the "start_suffix" field.
+func (du *DecisionUpdate) AddStartSuffix(i int64) *DecisionUpdate {
+	du.mutation.AddStartSuffix(i)
+	return du
+}
+
+// ClearStartSuffix clears the value of the "start_suffix" field.
+func (du *DecisionUpdate) ClearStartSuffix() *DecisionUpdate {
+	du.mutation.ClearStartSuffix()
+	return du
+}
+
+// SetEndSuffix sets the "end_suffix" field.
+func (du *DecisionUpdate) SetEndSuffix(i int64) *DecisionUpdate {
+	du.mutation.ResetEndSuffix()
+	du.mutation.SetEndSuffix(i)
+	return du
+}
+
+// SetNillableEndSuffix sets the "end_suffix" field if the given value is not nil.
+func (du *DecisionUpdate) SetNillableEndSuffix(i *int64) *DecisionUpdate {
+	if i != nil {
+		du.SetEndSuffix(*i)
+	}
+	return du
+}
+
+// AddEndSuffix adds i to the "end_suffix" field.
+func (du *DecisionUpdate) AddEndSuffix(i int64) *DecisionUpdate {
+	du.mutation.AddEndSuffix(i)
+	return du
+}
+
+// ClearEndSuffix clears the value of the "end_suffix" field.
+func (du *DecisionUpdate) ClearEndSuffix() *DecisionUpdate {
+	du.mutation.ClearEndSuffix()
+	return du
+}
+
+// SetIPSize sets the "ip_size" field.
+func (du *DecisionUpdate) SetIPSize(i int64) *DecisionUpdate {
+	du.mutation.ResetIPSize()
+	du.mutation.SetIPSize(i)
+	return du
+}
+
+// SetNillableIPSize sets the "ip_size" field if the given value is not nil.
+func (du *DecisionUpdate) SetNillableIPSize(i *int64) *DecisionUpdate {
+	if i != nil {
+		du.SetIPSize(*i)
+	}
+	return du
+}
+
+// AddIPSize adds i to the "ip_size" field.
+func (du *DecisionUpdate) AddIPSize(i int64) *DecisionUpdate {
+	du.mutation.AddIPSize(i)
+	return du
+}
+
+// ClearIPSize clears the value of the "ip_size" field.
+func (du *DecisionUpdate) ClearIPSize() *DecisionUpdate {
+	du.mutation.ClearIPSize()
+	return du
+}
+
+// SetScope sets the "scope" field.
 func (du *DecisionUpdate) SetScope(s string) *DecisionUpdate {
 	du.mutation.SetScope(s)
 	return du
 }
 
-// SetValue sets the value field.
+// SetValue sets the "value" field.
 func (du *DecisionUpdate) SetValue(s string) *DecisionUpdate {
 	du.mutation.SetValue(s)
 	return du
 }
 
-// SetOrigin sets the origin field.
+// SetOrigin sets the "origin" field.
 func (du *DecisionUpdate) SetOrigin(s string) *DecisionUpdate {
 	du.mutation.SetOrigin(s)
 	return du
 }
 
-// SetSimulated sets the simulated field.
+// SetSimulated sets the "simulated" field.
 func (du *DecisionUpdate) SetSimulated(b bool) *DecisionUpdate {
 	du.mutation.SetSimulated(b)
 	return du
 }
 
-// SetNillableSimulated sets the simulated field if the given value is not nil.
+// SetNillableSimulated sets the "simulated" field if the given value is not nil.
 func (du *DecisionUpdate) SetNillableSimulated(b *bool) *DecisionUpdate {
 	if b != nil {
 		du.SetSimulated(*b)
@@ -161,13 +241,13 @@ func (du *DecisionUpdate) SetNillableSimulated(b *bool) *DecisionUpdate {
 	return du
 }
 
-// SetOwnerID sets the owner edge to Alert by id.
+// SetOwnerID sets the "owner" edge to the Alert entity by ID.
 func (du *DecisionUpdate) SetOwnerID(id int) *DecisionUpdate {
 	du.mutation.SetOwnerID(id)
 	return du
 }
 
-// SetNillableOwnerID sets the owner edge to Alert by id if the given value is not nil.
+// SetNillableOwnerID sets the "owner" edge to the Alert entity by ID if the given value is not nil.
 func (du *DecisionUpdate) SetNillableOwnerID(id *int) *DecisionUpdate {
 	if id != nil {
 		du = du.SetOwnerID(*id)
@@ -175,7 +255,7 @@ func (du *DecisionUpdate) SetNillableOwnerID(id *int) *DecisionUpdate {
 	return du
 }
 
-// SetOwner sets the owner edge to Alert.
+// SetOwner sets the "owner" edge to the Alert entity.
 func (du *DecisionUpdate) SetOwner(a *Alert) *DecisionUpdate {
 	return du.SetOwnerID(a.ID)
 }
@@ -185,13 +265,13 @@ func (du *DecisionUpdate) Mutation() *DecisionMutation {
 	return du.mutation
 }
 
-// ClearOwner clears the "owner" edge to type Alert.
+// ClearOwner clears the "owner" edge to the Alert entity.
 func (du *DecisionUpdate) ClearOwner() *DecisionUpdate {
 	du.mutation.ClearOwner()
 	return du
 }
 
-// Save executes the query and returns the number of rows/vertices matched by this operation.
+// Save executes the query and returns the number of nodes affected by the update operation.
 func (du *DecisionUpdate) Save(ctx context.Context) (int, error) {
 	var (
 		err      error
@@ -253,7 +333,7 @@ func (du *DecisionUpdate) sqlSave(ctx context.Context) (n int, err error) {
 			},
 		},
 	}
-	if ps := du.predicates; len(ps) > 0 {
+	if ps := du.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)
@@ -335,6 +415,66 @@ func (du *DecisionUpdate) sqlSave(ctx context.Context) (n int, err error) {
 			Column: decision.FieldEndIP,
 		})
 	}
+	if value, ok := du.mutation.StartSuffix(); ok {
+		_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldStartSuffix,
+		})
+	}
+	if value, ok := du.mutation.AddedStartSuffix(); ok {
+		_spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldStartSuffix,
+		})
+	}
+	if du.mutation.StartSuffixCleared() {
+		_spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Column: decision.FieldStartSuffix,
+		})
+	}
+	if value, ok := du.mutation.EndSuffix(); ok {
+		_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldEndSuffix,
+		})
+	}
+	if value, ok := du.mutation.AddedEndSuffix(); ok {
+		_spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldEndSuffix,
+		})
+	}
+	if du.mutation.EndSuffixCleared() {
+		_spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Column: decision.FieldEndSuffix,
+		})
+	}
+	if value, ok := du.mutation.IPSize(); ok {
+		_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldIPSize,
+		})
+	}
+	if value, ok := du.mutation.AddedIPSize(); ok {
+		_spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldIPSize,
+		})
+	}
+	if du.mutation.IPSizeCleared() {
+		_spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Column: decision.FieldIPSize,
+		})
+	}
 	if value, ok := du.mutation.Scope(); ok {
 		_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
 			Type:   field.TypeString,
@@ -416,13 +556,13 @@ type DecisionUpdateOne struct {
 	mutation *DecisionMutation
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (duo *DecisionUpdateOne) SetCreatedAt(t time.Time) *DecisionUpdateOne {
 	duo.mutation.SetCreatedAt(t)
 	return duo
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (duo *DecisionUpdateOne) SetNillableCreatedAt(t *time.Time) *DecisionUpdateOne {
 	if t != nil {
 		duo.SetCreatedAt(*t)
@@ -430,13 +570,13 @@ func (duo *DecisionUpdateOne) SetNillableCreatedAt(t *time.Time) *DecisionUpdate
 	return duo
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (duo *DecisionUpdateOne) SetUpdatedAt(t time.Time) *DecisionUpdateOne {
 	duo.mutation.SetUpdatedAt(t)
 	return duo
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (duo *DecisionUpdateOne) SetNillableUpdatedAt(t *time.Time) *DecisionUpdateOne {
 	if t != nil {
 		duo.SetUpdatedAt(*t)
@@ -444,32 +584,32 @@ func (duo *DecisionUpdateOne) SetNillableUpdatedAt(t *time.Time) *DecisionUpdate
 	return duo
 }
 
-// SetUntil sets the until field.
+// SetUntil sets the "until" field.
 func (duo *DecisionUpdateOne) SetUntil(t time.Time) *DecisionUpdateOne {
 	duo.mutation.SetUntil(t)
 	return duo
 }
 
-// SetScenario sets the scenario field.
+// SetScenario sets the "scenario" field.
 func (duo *DecisionUpdateOne) SetScenario(s string) *DecisionUpdateOne {
 	duo.mutation.SetScenario(s)
 	return duo
 }
 
-// SetType sets the type field.
+// SetType sets the "type" field.
 func (duo *DecisionUpdateOne) SetType(s string) *DecisionUpdateOne {
 	duo.mutation.SetType(s)
 	return duo
 }
 
-// SetStartIP sets the start_ip field.
+// SetStartIP sets the "start_ip" field.
 func (duo *DecisionUpdateOne) SetStartIP(i int64) *DecisionUpdateOne {
 	duo.mutation.ResetStartIP()
 	duo.mutation.SetStartIP(i)
 	return duo
 }
 
-// SetNillableStartIP sets the start_ip field if the given value is not nil.
+// SetNillableStartIP sets the "start_ip" field if the given value is not nil.
 func (duo *DecisionUpdateOne) SetNillableStartIP(i *int64) *DecisionUpdateOne {
 	if i != nil {
 		duo.SetStartIP(*i)
@@ -477,26 +617,26 @@ func (duo *DecisionUpdateOne) SetNillableStartIP(i *int64) *DecisionUpdateOne {
 	return duo
 }
 
-// AddStartIP adds i to start_ip.
+// AddStartIP adds i to the "start_ip" field.
 func (duo *DecisionUpdateOne) AddStartIP(i int64) *DecisionUpdateOne {
 	duo.mutation.AddStartIP(i)
 	return duo
 }
 
-// ClearStartIP clears the value of start_ip.
+// ClearStartIP clears the value of the "start_ip" field.
 func (duo *DecisionUpdateOne) ClearStartIP() *DecisionUpdateOne {
 	duo.mutation.ClearStartIP()
 	return duo
 }
 
-// SetEndIP sets the end_ip field.
+// SetEndIP sets the "end_ip" field.
 func (duo *DecisionUpdateOne) SetEndIP(i int64) *DecisionUpdateOne {
 	duo.mutation.ResetEndIP()
 	duo.mutation.SetEndIP(i)
 	return duo
 }
 
-// SetNillableEndIP sets the end_ip field if the given value is not nil.
+// SetNillableEndIP sets the "end_ip" field if the given value is not nil.
 func (duo *DecisionUpdateOne) SetNillableEndIP(i *int64) *DecisionUpdateOne {
 	if i != nil {
 		duo.SetEndIP(*i)
@@ -504,43 +644,124 @@ func (duo *DecisionUpdateOne) SetNillableEndIP(i *int64) *DecisionUpdateOne {
 	return duo
 }
 
-// AddEndIP adds i to end_ip.
+// AddEndIP adds i to the "end_ip" field.
 func (duo *DecisionUpdateOne) AddEndIP(i int64) *DecisionUpdateOne {
 	duo.mutation.AddEndIP(i)
 	return duo
 }
 
-// ClearEndIP clears the value of end_ip.
+// ClearEndIP clears the value of the "end_ip" field.
 func (duo *DecisionUpdateOne) ClearEndIP() *DecisionUpdateOne {
 	duo.mutation.ClearEndIP()
 	return duo
 }
 
-// SetScope sets the scope field.
+// SetStartSuffix sets the "start_suffix" field.
+func (duo *DecisionUpdateOne) SetStartSuffix(i int64) *DecisionUpdateOne {
+	duo.mutation.ResetStartSuffix()
+	duo.mutation.SetStartSuffix(i)
+	return duo
+}
+
+// SetNillableStartSuffix sets the "start_suffix" field if the given value is not nil.
+func (duo *DecisionUpdateOne) SetNillableStartSuffix(i *int64) *DecisionUpdateOne {
+	if i != nil {
+		duo.SetStartSuffix(*i)
+	}
+	return duo
+}
+
+// AddStartSuffix adds i to the "start_suffix" field.
+func (duo *DecisionUpdateOne) AddStartSuffix(i int64) *DecisionUpdateOne {
+	duo.mutation.AddStartSuffix(i)
+	return duo
+}
+
+// ClearStartSuffix clears the value of the "start_suffix" field.
+func (duo *DecisionUpdateOne) ClearStartSuffix() *DecisionUpdateOne {
+	duo.mutation.ClearStartSuffix()
+	return duo
+}
+
+// SetEndSuffix sets the "end_suffix" field.
+func (duo *DecisionUpdateOne) SetEndSuffix(i int64) *DecisionUpdateOne {
+	duo.mutation.ResetEndSuffix()
+	duo.mutation.SetEndSuffix(i)
+	return duo
+}
+
+// SetNillableEndSuffix sets the "end_suffix" field if the given value is not nil.
+func (duo *DecisionUpdateOne) SetNillableEndSuffix(i *int64) *DecisionUpdateOne {
+	if i != nil {
+		duo.SetEndSuffix(*i)
+	}
+	return duo
+}
+
+// AddEndSuffix adds i to the "end_suffix" field.
+func (duo *DecisionUpdateOne) AddEndSuffix(i int64) *DecisionUpdateOne {
+	duo.mutation.AddEndSuffix(i)
+	return duo
+}
+
+// ClearEndSuffix clears the value of the "end_suffix" field.
+func (duo *DecisionUpdateOne) ClearEndSuffix() *DecisionUpdateOne {
+	duo.mutation.ClearEndSuffix()
+	return duo
+}
+
+// SetIPSize sets the "ip_size" field.
+func (duo *DecisionUpdateOne) SetIPSize(i int64) *DecisionUpdateOne {
+	duo.mutation.ResetIPSize()
+	duo.mutation.SetIPSize(i)
+	return duo
+}
+
+// SetNillableIPSize sets the "ip_size" field if the given value is not nil.
+func (duo *DecisionUpdateOne) SetNillableIPSize(i *int64) *DecisionUpdateOne {
+	if i != nil {
+		duo.SetIPSize(*i)
+	}
+	return duo
+}
+
+// AddIPSize adds i to the "ip_size" field.
+func (duo *DecisionUpdateOne) AddIPSize(i int64) *DecisionUpdateOne {
+	duo.mutation.AddIPSize(i)
+	return duo
+}
+
+// ClearIPSize clears the value of the "ip_size" field.
+func (duo *DecisionUpdateOne) ClearIPSize() *DecisionUpdateOne {
+	duo.mutation.ClearIPSize()
+	return duo
+}
+
+// SetScope sets the "scope" field.
 func (duo *DecisionUpdateOne) SetScope(s string) *DecisionUpdateOne {
 	duo.mutation.SetScope(s)
 	return duo
 }
 
-// SetValue sets the value field.
+// SetValue sets the "value" field.
 func (duo *DecisionUpdateOne) SetValue(s string) *DecisionUpdateOne {
 	duo.mutation.SetValue(s)
 	return duo
 }
 
-// SetOrigin sets the origin field.
+// SetOrigin sets the "origin" field.
 func (duo *DecisionUpdateOne) SetOrigin(s string) *DecisionUpdateOne {
 	duo.mutation.SetOrigin(s)
 	return duo
 }
 
-// SetSimulated sets the simulated field.
+// SetSimulated sets the "simulated" field.
 func (duo *DecisionUpdateOne) SetSimulated(b bool) *DecisionUpdateOne {
 	duo.mutation.SetSimulated(b)
 	return duo
 }
 
-// SetNillableSimulated sets the simulated field if the given value is not nil.
+// SetNillableSimulated sets the "simulated" field if the given value is not nil.
 func (duo *DecisionUpdateOne) SetNillableSimulated(b *bool) *DecisionUpdateOne {
 	if b != nil {
 		duo.SetSimulated(*b)
@@ -548,13 +769,13 @@ func (duo *DecisionUpdateOne) SetNillableSimulated(b *bool) *DecisionUpdateOne {
 	return duo
 }
 
-// SetOwnerID sets the owner edge to Alert by id.
+// SetOwnerID sets the "owner" edge to the Alert entity by ID.
 func (duo *DecisionUpdateOne) SetOwnerID(id int) *DecisionUpdateOne {
 	duo.mutation.SetOwnerID(id)
 	return duo
 }
 
-// SetNillableOwnerID sets the owner edge to Alert by id if the given value is not nil.
+// SetNillableOwnerID sets the "owner" edge to the Alert entity by ID if the given value is not nil.
 func (duo *DecisionUpdateOne) SetNillableOwnerID(id *int) *DecisionUpdateOne {
 	if id != nil {
 		duo = duo.SetOwnerID(*id)
@@ -562,7 +783,7 @@ func (duo *DecisionUpdateOne) SetNillableOwnerID(id *int) *DecisionUpdateOne {
 	return duo
 }
 
-// SetOwner sets the owner edge to Alert.
+// SetOwner sets the "owner" edge to the Alert entity.
 func (duo *DecisionUpdateOne) SetOwner(a *Alert) *DecisionUpdateOne {
 	return duo.SetOwnerID(a.ID)
 }
@@ -572,13 +793,13 @@ func (duo *DecisionUpdateOne) Mutation() *DecisionMutation {
 	return duo.mutation
 }
 
-// ClearOwner clears the "owner" edge to type Alert.
+// ClearOwner clears the "owner" edge to the Alert entity.
 func (duo *DecisionUpdateOne) ClearOwner() *DecisionUpdateOne {
 	duo.mutation.ClearOwner()
 	return duo
 }
 
-// Save executes the query and returns the updated entity.
+// Save executes the query and returns the updated Decision entity.
 func (duo *DecisionUpdateOne) Save(ctx context.Context) (*Decision, error) {
 	var (
 		err  error
@@ -720,6 +941,66 @@ func (duo *DecisionUpdateOne) sqlSave(ctx context.Context) (_node *Decision, err
 			Column: decision.FieldEndIP,
 		})
 	}
+	if value, ok := duo.mutation.StartSuffix(); ok {
+		_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldStartSuffix,
+		})
+	}
+	if value, ok := duo.mutation.AddedStartSuffix(); ok {
+		_spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldStartSuffix,
+		})
+	}
+	if duo.mutation.StartSuffixCleared() {
+		_spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Column: decision.FieldStartSuffix,
+		})
+	}
+	if value, ok := duo.mutation.EndSuffix(); ok {
+		_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldEndSuffix,
+		})
+	}
+	if value, ok := duo.mutation.AddedEndSuffix(); ok {
+		_spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldEndSuffix,
+		})
+	}
+	if duo.mutation.EndSuffixCleared() {
+		_spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Column: decision.FieldEndSuffix,
+		})
+	}
+	if value, ok := duo.mutation.IPSize(); ok {
+		_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldIPSize,
+		})
+	}
+	if value, ok := duo.mutation.AddedIPSize(); ok {
+		_spec.Fields.Add = append(_spec.Fields.Add, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Value:  value,
+			Column: decision.FieldIPSize,
+		})
+	}
+	if duo.mutation.IPSizeCleared() {
+		_spec.Fields.Clear = append(_spec.Fields.Clear, &sqlgraph.FieldSpec{
+			Type:   field.TypeInt64,
+			Column: decision.FieldIPSize,
+		})
+	}
 	if value, ok := duo.mutation.Scope(); ok {
 		_spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{
 			Type:   field.TypeString,
@@ -785,7 +1066,7 @@ func (duo *DecisionUpdateOne) sqlSave(ctx context.Context) (_node *Decision, err
 	}
 	_node = &Decision{config: duo.config}
 	_spec.Assign = _node.assignValues
-	_spec.ScanValues = _node.scanValues()
+	_spec.ScanValues = _node.scanValues
 	if err = sqlgraph.UpdateNode(ctx, duo.driver, _spec); err != nil {
 		if _, ok := err.(*sqlgraph.NotFoundError); ok {
 			err = &NotFoundError{decision.Label}

+ 3 - 3
pkg/database/ent/ent.go

@@ -13,7 +13,7 @@ import (
 	"github.com/facebook/ent/dialect/sql/sqlgraph"
 )
 
-// ent aliases to avoid import conflict in user's code.
+// ent aliases to avoid import conflicts in user's code.
 type (
 	Op         = ent.Op
 	Hook       = ent.Hook
@@ -164,7 +164,7 @@ func IsNotFound(err error) bool {
 	return errors.As(err, &e)
 }
 
-// MaskNotFound masks nor found error.
+// MaskNotFound masks not found error.
 func MaskNotFound(err error) error {
 	if IsNotFound(err) {
 		return nil
@@ -258,7 +258,7 @@ func isSQLConstraintError(err error) (*ConstraintError, bool) {
 	return nil, false
 }
 
-// rollback calls to tx.Rollback and wraps the given error with the rollback error if occurred.
+// rollback calls tx.Rollback and wraps the given error with the rollback error if present.
 func rollback(tx dialect.Tx, err error) error {
 	if rerr := tx.Rollback(); rerr != nil {
 		err = fmt.Errorf("%s: %v", err.Error(), rerr)

+ 61 - 53
pkg/database/ent/event.go

@@ -55,81 +55,89 @@ func (e EventEdges) OwnerOrErr() (*Alert, error) {
 }
 
 // scanValues returns the types for scanning values from sql.Rows.
-func (*Event) scanValues() []interface{} {
-	return []interface{}{
-		&sql.NullInt64{},  // id
-		&sql.NullTime{},   // created_at
-		&sql.NullTime{},   // updated_at
-		&sql.NullTime{},   // time
-		&sql.NullString{}, // serialized
-	}
-}
-
-// fkValues returns the types for scanning foreign-keys values from sql.Rows.
-func (*Event) fkValues() []interface{} {
-	return []interface{}{
-		&sql.NullInt64{}, // alert_events
+func (*Event) scanValues(columns []string) ([]interface{}, error) {
+	values := make([]interface{}, len(columns))
+	for i := range columns {
+		switch columns[i] {
+		case event.FieldID:
+			values[i] = &sql.NullInt64{}
+		case event.FieldSerialized:
+			values[i] = &sql.NullString{}
+		case event.FieldCreatedAt, event.FieldUpdatedAt, event.FieldTime:
+			values[i] = &sql.NullTime{}
+		case event.ForeignKeys[0]: // alert_events
+			values[i] = &sql.NullInt64{}
+		default:
+			return nil, fmt.Errorf("unexpected column %q for type Event", columns[i])
+		}
 	}
+	return values, nil
 }
 
 // assignValues assigns the values that were returned from sql.Rows (after scanning)
 // to the Event fields.
-func (e *Event) assignValues(values ...interface{}) error {
-	if m, n := len(values), len(event.Columns); m < n {
+func (e *Event) assignValues(columns []string, values []interface{}) error {
+	if m, n := len(values), len(columns); m < n {
 		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
 	}
-	value, ok := values[0].(*sql.NullInt64)
-	if !ok {
-		return fmt.Errorf("unexpected type %T for field id", value)
-	}
-	e.ID = int(value.Int64)
-	values = values[1:]
-	if value, ok := values[0].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field created_at", values[0])
-	} else if value.Valid {
-		e.CreatedAt = value.Time
-	}
-	if value, ok := values[1].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field updated_at", values[1])
-	} else if value.Valid {
-		e.UpdatedAt = value.Time
-	}
-	if value, ok := values[2].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field time", values[2])
-	} else if value.Valid {
-		e.Time = value.Time
-	}
-	if value, ok := values[3].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field serialized", values[3])
-	} else if value.Valid {
-		e.Serialized = value.String
-	}
-	values = values[4:]
-	if len(values) == len(event.ForeignKeys) {
-		if value, ok := values[0].(*sql.NullInt64); !ok {
-			return fmt.Errorf("unexpected type %T for edge-field alert_events", value)
-		} else if value.Valid {
-			e.alert_events = new(int)
-			*e.alert_events = int(value.Int64)
+	for i := range columns {
+		switch columns[i] {
+		case event.FieldID:
+			value, ok := values[i].(*sql.NullInt64)
+			if !ok {
+				return fmt.Errorf("unexpected type %T for field id", value)
+			}
+			e.ID = int(value.Int64)
+		case event.FieldCreatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field created_at", values[i])
+			} else if value.Valid {
+				e.CreatedAt = value.Time
+			}
+		case event.FieldUpdatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field updated_at", values[i])
+			} else if value.Valid {
+				e.UpdatedAt = value.Time
+			}
+		case event.FieldTime:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field time", values[i])
+			} else if value.Valid {
+				e.Time = value.Time
+			}
+		case event.FieldSerialized:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field serialized", values[i])
+			} else if value.Valid {
+				e.Serialized = value.String
+			}
+		case event.ForeignKeys[0]:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for edge-field alert_events", value)
+			} else if value.Valid {
+				e.alert_events = new(int)
+				*e.alert_events = int(value.Int64)
+			}
 		}
 	}
 	return nil
 }
 
-// QueryOwner queries the owner edge of the Event.
+// QueryOwner queries the "owner" edge of the Event entity.
 func (e *Event) QueryOwner() *AlertQuery {
 	return (&EventClient{config: e.config}).QueryOwner(e)
 }
 
 // Update returns a builder for updating this Event.
-// Note that, you need to call Event.Unwrap() before calling this method, if this Event
+// Note that you need to call Event.Unwrap() before calling this method if this Event
 // was returned from a transaction, and the transaction was committed or rolled back.
 func (e *Event) Update() *EventUpdateOne {
 	return (&EventClient{config: e.config}).UpdateOne(e)
 }
 
-// Unwrap unwraps the entity that was returned from a transaction after it was closed,
-// so that all next queries will be executed through the driver which created the transaction.
+// Unwrap unwraps the Event entity that was returned from a transaction after it was closed,
+// so that all future queries will be executed through the driver which created the transaction.
 func (e *Event) Unwrap() *Event {
 	tx, ok := e.config.driver.(*txDriver)
 	if !ok {

+ 2 - 2
pkg/database/ent/event/event.go

@@ -64,9 +64,9 @@ func ValidColumn(column string) bool {
 }
 
 var (
-	// DefaultCreatedAt holds the default value on creation for the created_at field.
+	// DefaultCreatedAt holds the default value on creation for the "created_at" field.
 	DefaultCreatedAt func() time.Time
-	// DefaultUpdatedAt holds the default value on creation for the updated_at field.
+	// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
 	DefaultUpdatedAt func() time.Time
 	// SerializedValidator is a validator for the "serialized" field. It is called by the builders before save.
 	SerializedValidator func(string) error

+ 3 - 3
pkg/database/ent/event/where.go

@@ -10,7 +10,7 @@ import (
 	"github.com/facebook/ent/dialect/sql/sqlgraph"
 )
 
-// ID filters vertices based on their identifier.
+// ID filters vertices based on their ID field.
 func ID(id int) predicate.Event {
 	return predicate.Event(func(s *sql.Selector) {
 		s.Where(sql.EQ(s.C(FieldID), id))
@@ -488,7 +488,7 @@ func HasOwnerWith(preds ...predicate.Alert) predicate.Event {
 	})
 }
 
-// And groups list of predicates with the AND operator between them.
+// And groups predicates with the AND operator between them.
 func And(predicates ...predicate.Event) predicate.Event {
 	return predicate.Event(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)
@@ -499,7 +499,7 @@ func And(predicates ...predicate.Event) predicate.Event {
 	})
 }
 
-// Or groups list of predicates with the OR operator between them.
+// Or groups predicates with the OR operator between them.
 func Or(predicates ...predicate.Event) predicate.Event {
 	return predicate.Event(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)

+ 11 - 11
pkg/database/ent/event_create.go

@@ -21,13 +21,13 @@ type EventCreate struct {
 	hooks    []Hook
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (ec *EventCreate) SetCreatedAt(t time.Time) *EventCreate {
 	ec.mutation.SetCreatedAt(t)
 	return ec
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (ec *EventCreate) SetNillableCreatedAt(t *time.Time) *EventCreate {
 	if t != nil {
 		ec.SetCreatedAt(*t)
@@ -35,13 +35,13 @@ func (ec *EventCreate) SetNillableCreatedAt(t *time.Time) *EventCreate {
 	return ec
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (ec *EventCreate) SetUpdatedAt(t time.Time) *EventCreate {
 	ec.mutation.SetUpdatedAt(t)
 	return ec
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (ec *EventCreate) SetNillableUpdatedAt(t *time.Time) *EventCreate {
 	if t != nil {
 		ec.SetUpdatedAt(*t)
@@ -49,25 +49,25 @@ func (ec *EventCreate) SetNillableUpdatedAt(t *time.Time) *EventCreate {
 	return ec
 }
 
-// SetTime sets the time field.
+// SetTime sets the "time" field.
 func (ec *EventCreate) SetTime(t time.Time) *EventCreate {
 	ec.mutation.SetTime(t)
 	return ec
 }
 
-// SetSerialized sets the serialized field.
+// SetSerialized sets the "serialized" field.
 func (ec *EventCreate) SetSerialized(s string) *EventCreate {
 	ec.mutation.SetSerialized(s)
 	return ec
 }
 
-// SetOwnerID sets the owner edge to Alert by id.
+// SetOwnerID sets the "owner" edge to the Alert entity by ID.
 func (ec *EventCreate) SetOwnerID(id int) *EventCreate {
 	ec.mutation.SetOwnerID(id)
 	return ec
 }
 
-// SetNillableOwnerID sets the owner edge to Alert by id if the given value is not nil.
+// SetNillableOwnerID sets the "owner" edge to the Alert entity by ID if the given value is not nil.
 func (ec *EventCreate) SetNillableOwnerID(id *int) *EventCreate {
 	if id != nil {
 		ec = ec.SetOwnerID(*id)
@@ -75,7 +75,7 @@ func (ec *EventCreate) SetNillableOwnerID(id *int) *EventCreate {
 	return ec
 }
 
-// SetOwner sets the owner edge to Alert.
+// SetOwner sets the "owner" edge to the Alert entity.
 func (ec *EventCreate) SetOwner(a *Alert) *EventCreate {
 	return ec.SetOwnerID(a.ID)
 }
@@ -242,7 +242,7 @@ func (ec *EventCreate) createSpec() (*Event, *sqlgraph.CreateSpec) {
 	return _node, _spec
 }
 
-// EventCreateBulk is the builder for creating a bulk of Event entities.
+// EventCreateBulk is the builder for creating many Event entities in bulk.
 type EventCreateBulk struct {
 	config
 	builders []*EventCreate
@@ -300,7 +300,7 @@ func (ecb *EventCreateBulk) Save(ctx context.Context) ([]*Event, error) {
 	return nodes, nil
 }
 
-// SaveX calls Save and panics if Save returns an error.
+// SaveX is like Save, but panics if an error occurs.
 func (ecb *EventCreateBulk) SaveX(ctx context.Context) []*Event {
 	v, err := ecb.Save(ctx)
 	if err != nil {

+ 5 - 6
pkg/database/ent/event_delete.go

@@ -16,14 +16,13 @@ import (
 // EventDelete is the builder for deleting a Event entity.
 type EventDelete struct {
 	config
-	hooks      []Hook
-	mutation   *EventMutation
-	predicates []predicate.Event
+	hooks    []Hook
+	mutation *EventMutation
 }
 
-// Where adds a new predicate to the delete builder.
+// Where adds a new predicate to the EventDelete builder.
 func (ed *EventDelete) Where(ps ...predicate.Event) *EventDelete {
-	ed.predicates = append(ed.predicates, ps...)
+	ed.mutation.predicates = append(ed.mutation.predicates, ps...)
 	return ed
 }
 
@@ -75,7 +74,7 @@ func (ed *EventDelete) sqlExec(ctx context.Context) (int, error) {
 			},
 		},
 	}
-	if ps := ed.predicates; len(ps) > 0 {
+	if ps := ed.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)

+ 78 - 65
pkg/database/ent/event_query.go

@@ -22,7 +22,7 @@ type EventQuery struct {
 	limit      *int
 	offset     *int
 	order      []OrderFunc
-	unique     []string
+	fields     []string
 	predicates []predicate.Event
 	// eager-loading edges.
 	withOwner *AlertQuery
@@ -32,7 +32,7 @@ type EventQuery struct {
 	path func(context.Context) (*sql.Selector, error)
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the EventQuery builder.
 func (eq *EventQuery) Where(ps ...predicate.Event) *EventQuery {
 	eq.predicates = append(eq.predicates, ps...)
 	return eq
@@ -56,7 +56,7 @@ func (eq *EventQuery) Order(o ...OrderFunc) *EventQuery {
 	return eq
 }
 
-// QueryOwner chains the current query on the owner edge.
+// QueryOwner chains the current query on the "owner" edge.
 func (eq *EventQuery) QueryOwner() *AlertQuery {
 	query := &AlertQuery{config: eq.config}
 	query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
@@ -78,7 +78,8 @@ func (eq *EventQuery) QueryOwner() *AlertQuery {
 	return query
 }
 
-// First returns the first Event entity in the query. Returns *NotFoundError when no event was found.
+// First returns the first Event entity from the query.
+// Returns a *NotFoundError when no Event was found.
 func (eq *EventQuery) First(ctx context.Context) (*Event, error) {
 	nodes, err := eq.Limit(1).All(ctx)
 	if err != nil {
@@ -99,7 +100,8 @@ func (eq *EventQuery) FirstX(ctx context.Context) *Event {
 	return node
 }
 
-// FirstID returns the first Event id in the query. Returns *NotFoundError when no id was found.
+// FirstID returns the first Event ID from the query.
+// Returns a *NotFoundError when no Event ID was found.
 func (eq *EventQuery) FirstID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = eq.Limit(1).IDs(ctx); err != nil {
@@ -112,8 +114,8 @@ func (eq *EventQuery) FirstID(ctx context.Context) (id int, err error) {
 	return ids[0], nil
 }
 
-// FirstXID is like FirstID, but panics if an error occurs.
-func (eq *EventQuery) FirstXID(ctx context.Context) int {
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (eq *EventQuery) FirstIDX(ctx context.Context) int {
 	id, err := eq.FirstID(ctx)
 	if err != nil && !IsNotFound(err) {
 		panic(err)
@@ -121,7 +123,9 @@ func (eq *EventQuery) FirstXID(ctx context.Context) int {
 	return id
 }
 
-// Only returns the only Event entity in the query, returns an error if not exactly one entity was returned.
+// Only returns a single Event entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when exactly one Event entity is not found.
+// Returns a *NotFoundError when no Event entities are found.
 func (eq *EventQuery) Only(ctx context.Context) (*Event, error) {
 	nodes, err := eq.Limit(2).All(ctx)
 	if err != nil {
@@ -146,7 +150,9 @@ func (eq *EventQuery) OnlyX(ctx context.Context) *Event {
 	return node
 }
 
-// OnlyID returns the only Event id in the query, returns an error if not exactly one id was returned.
+// OnlyID is like Only, but returns the only Event ID in the query.
+// Returns a *NotSingularError when exactly one Event ID is not found.
+// Returns a *NotFoundError when no entities are found.
 func (eq *EventQuery) OnlyID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = eq.Limit(2).IDs(ctx); err != nil {
@@ -189,7 +195,7 @@ func (eq *EventQuery) AllX(ctx context.Context) []*Event {
 	return nodes
 }
 
-// IDs executes the query and returns a list of Event ids.
+// IDs executes the query and returns a list of Event IDs.
 func (eq *EventQuery) IDs(ctx context.Context) ([]int, error) {
 	var ids []int
 	if err := eq.Select(event.FieldID).Scan(ctx, &ids); err != nil {
@@ -241,24 +247,27 @@ func (eq *EventQuery) ExistX(ctx context.Context) bool {
 	return exist
 }
 
-// Clone returns a duplicate of the query builder, including all associated steps. It can be
+// Clone returns a duplicate of the EventQuery builder, including all associated steps. It can be
 // used to prepare common query builders and use them differently after the clone is made.
 func (eq *EventQuery) Clone() *EventQuery {
+	if eq == nil {
+		return nil
+	}
 	return &EventQuery{
 		config:     eq.config,
 		limit:      eq.limit,
 		offset:     eq.offset,
 		order:      append([]OrderFunc{}, eq.order...),
-		unique:     append([]string{}, eq.unique...),
 		predicates: append([]predicate.Event{}, eq.predicates...),
+		withOwner:  eq.withOwner.Clone(),
 		// clone intermediate query.
 		sql:  eq.sql.Clone(),
 		path: eq.path,
 	}
 }
 
-//  WithOwner tells the query-builder to eager-loads the nodes that are connected to
-// the "owner" edge. The optional arguments used to configure the query builder of the edge.
+// WithOwner tells the query-builder to eager-load the nodes that are connected to
+// the "owner" edge. The optional arguments are used to configure the query builder of the edge.
 func (eq *EventQuery) WithOwner(opts ...func(*AlertQuery)) *EventQuery {
 	query := &AlertQuery{config: eq.config}
 	for _, opt := range opts {
@@ -268,7 +277,7 @@ func (eq *EventQuery) WithOwner(opts ...func(*AlertQuery)) *EventQuery {
 	return eq
 }
 
-// GroupBy used to group vertices by one or more fields/columns.
+// GroupBy is used to group vertices by one or more fields/columns.
 // It is often used with aggregate functions, like: count, max, mean, min, sum.
 //
 // Example:
@@ -295,7 +304,8 @@ func (eq *EventQuery) GroupBy(field string, fields ...string) *EventGroupBy {
 	return group
 }
 
-// Select one or more fields from the given query.
+// Select allows the selection one or more fields/columns for the given query,
+// instead of selecting all fields in the entity.
 //
 // Example:
 //
@@ -308,18 +318,16 @@ func (eq *EventQuery) GroupBy(field string, fields ...string) *EventGroupBy {
 //		Scan(ctx, &v)
 //
 func (eq *EventQuery) Select(field string, fields ...string) *EventSelect {
-	selector := &EventSelect{config: eq.config}
-	selector.fields = append([]string{field}, fields...)
-	selector.path = func(ctx context.Context) (prev *sql.Selector, err error) {
-		if err := eq.prepareQuery(ctx); err != nil {
-			return nil, err
-		}
-		return eq.sqlQuery(), nil
-	}
-	return selector
+	eq.fields = append([]string{field}, fields...)
+	return &EventSelect{EventQuery: eq}
 }
 
 func (eq *EventQuery) prepareQuery(ctx context.Context) error {
+	for _, f := range eq.fields {
+		if !event.ValidColumn(f) {
+			return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+		}
+	}
 	if eq.path != nil {
 		prev, err := eq.path(ctx)
 		if err != nil {
@@ -345,22 +353,18 @@ func (eq *EventQuery) sqlAll(ctx context.Context) ([]*Event, error) {
 	if withFKs {
 		_spec.Node.Columns = append(_spec.Node.Columns, event.ForeignKeys...)
 	}
-	_spec.ScanValues = func() []interface{} {
+	_spec.ScanValues = func(columns []string) ([]interface{}, error) {
 		node := &Event{config: eq.config}
 		nodes = append(nodes, node)
-		values := node.scanValues()
-		if withFKs {
-			values = append(values, node.fkValues()...)
-		}
-		return values
+		return node.scanValues(columns)
 	}
-	_spec.Assign = func(values ...interface{}) error {
+	_spec.Assign = func(columns []string, values []interface{}) error {
 		if len(nodes) == 0 {
 			return fmt.Errorf("ent: Assign called without calling ScanValues")
 		}
 		node := nodes[len(nodes)-1]
 		node.Edges.loadedTypes = loadedTypes
-		return node.assignValues(values...)
+		return node.assignValues(columns, values)
 	}
 	if err := sqlgraph.QueryNodes(ctx, eq.driver, _spec); err != nil {
 		return nil, err
@@ -423,6 +427,15 @@ func (eq *EventQuery) querySpec() *sqlgraph.QuerySpec {
 		From:   eq.sql,
 		Unique: true,
 	}
+	if fields := eq.fields; len(fields) > 0 {
+		_spec.Node.Columns = make([]string, 0, len(fields))
+		_spec.Node.Columns = append(_spec.Node.Columns, event.FieldID)
+		for i := range fields {
+			if fields[i] != event.FieldID {
+				_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+			}
+		}
+	}
 	if ps := eq.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
@@ -471,7 +484,7 @@ func (eq *EventQuery) sqlQuery() *sql.Selector {
 	return selector
 }
 
-// EventGroupBy is the builder for group-by Event entities.
+// EventGroupBy is the group-by builder for Event entities.
 type EventGroupBy struct {
 	config
 	fields []string
@@ -487,7 +500,7 @@ func (egb *EventGroupBy) Aggregate(fns ...AggregateFunc) *EventGroupBy {
 	return egb
 }
 
-// Scan applies the group-by query and scan the result into the given value.
+// Scan applies the group-by query and scans the result into the given value.
 func (egb *EventGroupBy) Scan(ctx context.Context, v interface{}) error {
 	query, err := egb.path(ctx)
 	if err != nil {
@@ -504,7 +517,8 @@ func (egb *EventGroupBy) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from group-by. It is only allowed when querying group-by with one field.
+// Strings returns list of strings from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (egb *EventGroupBy) Strings(ctx context.Context) ([]string, error) {
 	if len(egb.fields) > 1 {
 		return nil, errors.New("ent: EventGroupBy.Strings is not achievable when grouping more than 1 field")
@@ -525,7 +539,8 @@ func (egb *EventGroupBy) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from group-by. It is only allowed when querying group-by with one field.
+// String returns a single string from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (egb *EventGroupBy) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = egb.Strings(ctx); err != nil {
@@ -551,7 +566,8 @@ func (egb *EventGroupBy) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from group-by. It is only allowed when querying group-by with one field.
+// Ints returns list of ints from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (egb *EventGroupBy) Ints(ctx context.Context) ([]int, error) {
 	if len(egb.fields) > 1 {
 		return nil, errors.New("ent: EventGroupBy.Ints is not achievable when grouping more than 1 field")
@@ -572,7 +588,8 @@ func (egb *EventGroupBy) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from group-by. It is only allowed when querying group-by with one field.
+// Int returns a single int from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (egb *EventGroupBy) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = egb.Ints(ctx); err != nil {
@@ -598,7 +615,8 @@ func (egb *EventGroupBy) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from group-by. It is only allowed when querying group-by with one field.
+// Float64s returns list of float64s from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (egb *EventGroupBy) Float64s(ctx context.Context) ([]float64, error) {
 	if len(egb.fields) > 1 {
 		return nil, errors.New("ent: EventGroupBy.Float64s is not achievable when grouping more than 1 field")
@@ -619,7 +637,8 @@ func (egb *EventGroupBy) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from group-by. It is only allowed when querying group-by with one field.
+// Float64 returns a single float64 from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (egb *EventGroupBy) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = egb.Float64s(ctx); err != nil {
@@ -645,7 +664,8 @@ func (egb *EventGroupBy) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from group-by. It is only allowed when querying group-by with one field.
+// Bools returns list of bools from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (egb *EventGroupBy) Bools(ctx context.Context) ([]bool, error) {
 	if len(egb.fields) > 1 {
 		return nil, errors.New("ent: EventGroupBy.Bools is not achievable when grouping more than 1 field")
@@ -666,7 +686,8 @@ func (egb *EventGroupBy) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from group-by. It is only allowed when querying group-by with one field.
+// Bool returns a single bool from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (egb *EventGroupBy) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = egb.Bools(ctx); err != nil {
@@ -721,22 +742,19 @@ func (egb *EventGroupBy) sqlQuery() *sql.Selector {
 	return selector.Select(columns...).GroupBy(egb.fields...)
 }
 
-// EventSelect is the builder for select fields of Event entities.
+// EventSelect is the builder for selecting fields of Event entities.
 type EventSelect struct {
-	config
-	fields []string
+	*EventQuery
 	// intermediate query (i.e. traversal path).
-	sql  *sql.Selector
-	path func(context.Context) (*sql.Selector, error)
+	sql *sql.Selector
 }
 
-// Scan applies the selector query and scan the result into the given value.
+// Scan applies the selector query and scans the result into the given value.
 func (es *EventSelect) Scan(ctx context.Context, v interface{}) error {
-	query, err := es.path(ctx)
-	if err != nil {
+	if err := es.prepareQuery(ctx); err != nil {
 		return err
 	}
-	es.sql = query
+	es.sql = es.EventQuery.sqlQuery()
 	return es.sqlScan(ctx, v)
 }
 
@@ -747,7 +765,7 @@ func (es *EventSelect) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from selector. It is only allowed when selecting one field.
+// Strings returns list of strings from a selector. It is only allowed when selecting one field.
 func (es *EventSelect) Strings(ctx context.Context) ([]string, error) {
 	if len(es.fields) > 1 {
 		return nil, errors.New("ent: EventSelect.Strings is not achievable when selecting more than 1 field")
@@ -768,7 +786,7 @@ func (es *EventSelect) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from selector. It is only allowed when selecting one field.
+// String returns a single string from a selector. It is only allowed when selecting one field.
 func (es *EventSelect) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = es.Strings(ctx); err != nil {
@@ -794,7 +812,7 @@ func (es *EventSelect) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from selector. It is only allowed when selecting one field.
+// Ints returns list of ints from a selector. It is only allowed when selecting one field.
 func (es *EventSelect) Ints(ctx context.Context) ([]int, error) {
 	if len(es.fields) > 1 {
 		return nil, errors.New("ent: EventSelect.Ints is not achievable when selecting more than 1 field")
@@ -815,7 +833,7 @@ func (es *EventSelect) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from selector. It is only allowed when selecting one field.
+// Int returns a single int from a selector. It is only allowed when selecting one field.
 func (es *EventSelect) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = es.Ints(ctx); err != nil {
@@ -841,7 +859,7 @@ func (es *EventSelect) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from selector. It is only allowed when selecting one field.
+// Float64s returns list of float64s from a selector. It is only allowed when selecting one field.
 func (es *EventSelect) Float64s(ctx context.Context) ([]float64, error) {
 	if len(es.fields) > 1 {
 		return nil, errors.New("ent: EventSelect.Float64s is not achievable when selecting more than 1 field")
@@ -862,7 +880,7 @@ func (es *EventSelect) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from selector. It is only allowed when selecting one field.
+// Float64 returns a single float64 from a selector. It is only allowed when selecting one field.
 func (es *EventSelect) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = es.Float64s(ctx); err != nil {
@@ -888,7 +906,7 @@ func (es *EventSelect) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from selector. It is only allowed when selecting one field.
+// Bools returns list of bools from a selector. It is only allowed when selecting one field.
 func (es *EventSelect) Bools(ctx context.Context) ([]bool, error) {
 	if len(es.fields) > 1 {
 		return nil, errors.New("ent: EventSelect.Bools is not achievable when selecting more than 1 field")
@@ -909,7 +927,7 @@ func (es *EventSelect) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from selector. It is only allowed when selecting one field.
+// Bool returns a single bool from a selector. It is only allowed when selecting one field.
 func (es *EventSelect) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = es.Bools(ctx); err != nil {
@@ -936,11 +954,6 @@ func (es *EventSelect) BoolX(ctx context.Context) bool {
 }
 
 func (es *EventSelect) sqlScan(ctx context.Context, v interface{}) error {
-	for _, f := range es.fields {
-		if !event.ValidColumn(f) {
-			return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for selection", f)}
-		}
-	}
 	rows := &sql.Rows{}
 	query, args := es.sqlQuery().Query()
 	if err := es.driver.Query(ctx, query, args, rows); err != nil {

+ 28 - 29
pkg/database/ent/event_update.go

@@ -18,24 +18,23 @@ import (
 // EventUpdate is the builder for updating Event entities.
 type EventUpdate struct {
 	config
-	hooks      []Hook
-	mutation   *EventMutation
-	predicates []predicate.Event
+	hooks    []Hook
+	mutation *EventMutation
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the EventUpdate builder.
 func (eu *EventUpdate) Where(ps ...predicate.Event) *EventUpdate {
-	eu.predicates = append(eu.predicates, ps...)
+	eu.mutation.predicates = append(eu.mutation.predicates, ps...)
 	return eu
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (eu *EventUpdate) SetCreatedAt(t time.Time) *EventUpdate {
 	eu.mutation.SetCreatedAt(t)
 	return eu
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (eu *EventUpdate) SetNillableCreatedAt(t *time.Time) *EventUpdate {
 	if t != nil {
 		eu.SetCreatedAt(*t)
@@ -43,13 +42,13 @@ func (eu *EventUpdate) SetNillableCreatedAt(t *time.Time) *EventUpdate {
 	return eu
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (eu *EventUpdate) SetUpdatedAt(t time.Time) *EventUpdate {
 	eu.mutation.SetUpdatedAt(t)
 	return eu
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (eu *EventUpdate) SetNillableUpdatedAt(t *time.Time) *EventUpdate {
 	if t != nil {
 		eu.SetUpdatedAt(*t)
@@ -57,25 +56,25 @@ func (eu *EventUpdate) SetNillableUpdatedAt(t *time.Time) *EventUpdate {
 	return eu
 }
 
-// SetTime sets the time field.
+// SetTime sets the "time" field.
 func (eu *EventUpdate) SetTime(t time.Time) *EventUpdate {
 	eu.mutation.SetTime(t)
 	return eu
 }
 
-// SetSerialized sets the serialized field.
+// SetSerialized sets the "serialized" field.
 func (eu *EventUpdate) SetSerialized(s string) *EventUpdate {
 	eu.mutation.SetSerialized(s)
 	return eu
 }
 
-// SetOwnerID sets the owner edge to Alert by id.
+// SetOwnerID sets the "owner" edge to the Alert entity by ID.
 func (eu *EventUpdate) SetOwnerID(id int) *EventUpdate {
 	eu.mutation.SetOwnerID(id)
 	return eu
 }
 
-// SetNillableOwnerID sets the owner edge to Alert by id if the given value is not nil.
+// SetNillableOwnerID sets the "owner" edge to the Alert entity by ID if the given value is not nil.
 func (eu *EventUpdate) SetNillableOwnerID(id *int) *EventUpdate {
 	if id != nil {
 		eu = eu.SetOwnerID(*id)
@@ -83,7 +82,7 @@ func (eu *EventUpdate) SetNillableOwnerID(id *int) *EventUpdate {
 	return eu
 }
 
-// SetOwner sets the owner edge to Alert.
+// SetOwner sets the "owner" edge to the Alert entity.
 func (eu *EventUpdate) SetOwner(a *Alert) *EventUpdate {
 	return eu.SetOwnerID(a.ID)
 }
@@ -93,13 +92,13 @@ func (eu *EventUpdate) Mutation() *EventMutation {
 	return eu.mutation
 }
 
-// ClearOwner clears the "owner" edge to type Alert.
+// ClearOwner clears the "owner" edge to the Alert entity.
 func (eu *EventUpdate) ClearOwner() *EventUpdate {
 	eu.mutation.ClearOwner()
 	return eu
 }
 
-// Save executes the query and returns the number of rows/vertices matched by this operation.
+// Save executes the query and returns the number of nodes affected by the update operation.
 func (eu *EventUpdate) Save(ctx context.Context) (int, error) {
 	var (
 		err      error
@@ -177,7 +176,7 @@ func (eu *EventUpdate) sqlSave(ctx context.Context) (n int, err error) {
 			},
 		},
 	}
-	if ps := eu.predicates; len(ps) > 0 {
+	if ps := eu.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)
@@ -265,13 +264,13 @@ type EventUpdateOne struct {
 	mutation *EventMutation
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (euo *EventUpdateOne) SetCreatedAt(t time.Time) *EventUpdateOne {
 	euo.mutation.SetCreatedAt(t)
 	return euo
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (euo *EventUpdateOne) SetNillableCreatedAt(t *time.Time) *EventUpdateOne {
 	if t != nil {
 		euo.SetCreatedAt(*t)
@@ -279,13 +278,13 @@ func (euo *EventUpdateOne) SetNillableCreatedAt(t *time.Time) *EventUpdateOne {
 	return euo
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (euo *EventUpdateOne) SetUpdatedAt(t time.Time) *EventUpdateOne {
 	euo.mutation.SetUpdatedAt(t)
 	return euo
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (euo *EventUpdateOne) SetNillableUpdatedAt(t *time.Time) *EventUpdateOne {
 	if t != nil {
 		euo.SetUpdatedAt(*t)
@@ -293,25 +292,25 @@ func (euo *EventUpdateOne) SetNillableUpdatedAt(t *time.Time) *EventUpdateOne {
 	return euo
 }
 
-// SetTime sets the time field.
+// SetTime sets the "time" field.
 func (euo *EventUpdateOne) SetTime(t time.Time) *EventUpdateOne {
 	euo.mutation.SetTime(t)
 	return euo
 }
 
-// SetSerialized sets the serialized field.
+// SetSerialized sets the "serialized" field.
 func (euo *EventUpdateOne) SetSerialized(s string) *EventUpdateOne {
 	euo.mutation.SetSerialized(s)
 	return euo
 }
 
-// SetOwnerID sets the owner edge to Alert by id.
+// SetOwnerID sets the "owner" edge to the Alert entity by ID.
 func (euo *EventUpdateOne) SetOwnerID(id int) *EventUpdateOne {
 	euo.mutation.SetOwnerID(id)
 	return euo
 }
 
-// SetNillableOwnerID sets the owner edge to Alert by id if the given value is not nil.
+// SetNillableOwnerID sets the "owner" edge to the Alert entity by ID if the given value is not nil.
 func (euo *EventUpdateOne) SetNillableOwnerID(id *int) *EventUpdateOne {
 	if id != nil {
 		euo = euo.SetOwnerID(*id)
@@ -319,7 +318,7 @@ func (euo *EventUpdateOne) SetNillableOwnerID(id *int) *EventUpdateOne {
 	return euo
 }
 
-// SetOwner sets the owner edge to Alert.
+// SetOwner sets the "owner" edge to the Alert entity.
 func (euo *EventUpdateOne) SetOwner(a *Alert) *EventUpdateOne {
 	return euo.SetOwnerID(a.ID)
 }
@@ -329,13 +328,13 @@ func (euo *EventUpdateOne) Mutation() *EventMutation {
 	return euo.mutation
 }
 
-// ClearOwner clears the "owner" edge to type Alert.
+// ClearOwner clears the "owner" edge to the Alert entity.
 func (euo *EventUpdateOne) ClearOwner() *EventUpdateOne {
 	euo.mutation.ClearOwner()
 	return euo
 }
 
-// Save executes the query and returns the updated entity.
+// Save executes the query and returns the updated Event entity.
 func (euo *EventUpdateOne) Save(ctx context.Context) (*Event, error) {
 	var (
 		err  error
@@ -483,7 +482,7 @@ func (euo *EventUpdateOne) sqlSave(ctx context.Context) (_node *Event, err error
 	}
 	_node = &Event{config: euo.config}
 	_spec.Assign = _node.assignValues
-	_spec.ScanValues = _node.scanValues()
+	_spec.ScanValues = _node.scanValues
 	if err = sqlgraph.UpdateNode(ctx, euo.driver, _spec); err != nil {
 		if _, ok := err.(*sqlgraph.NotFoundError); ok {
 			err = &NotFoundError{event.Label}

+ 10 - 5
pkg/database/ent/hook/hook.go

@@ -210,6 +210,15 @@ func Unless(hk ent.Hook, op ent.Op) ent.Hook {
 	return If(hk, Not(HasOp(op)))
 }
 
+// FixedError is a hook returning a fixed error.
+func FixedError(err error) ent.Hook {
+	return func(ent.Mutator) ent.Mutator {
+		return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) {
+			return nil, err
+		})
+	}
+}
+
 // Reject returns a hook that rejects all operations that match op.
 //
 //	func (T) Hooks() []ent.Hook {
@@ -219,11 +228,7 @@ func Unless(hk ent.Hook, op ent.Op) ent.Hook {
 //	}
 //
 func Reject(op ent.Op) ent.Hook {
-	hk := func(ent.Mutator) ent.Mutator {
-		return ent.MutateFunc(func(_ context.Context, m ent.Mutation) (ent.Value, error) {
-			return nil, fmt.Errorf("%s operation is not allowed", m.Op())
-		})
-	}
+	hk := FixedError(fmt.Errorf("%s operation is not allowed", op))
 	return On(hk, op)
 }
 

+ 85 - 68
pkg/database/ent/machine.go

@@ -58,95 +58,112 @@ func (e MachineEdges) AlertsOrErr() ([]*Alert, error) {
 }
 
 // scanValues returns the types for scanning values from sql.Rows.
-func (*Machine) scanValues() []interface{} {
-	return []interface{}{
-		&sql.NullInt64{},  // id
-		&sql.NullTime{},   // created_at
-		&sql.NullTime{},   // updated_at
-		&sql.NullString{}, // machineId
-		&sql.NullString{}, // password
-		&sql.NullString{}, // ipAddress
-		&sql.NullString{}, // scenarios
-		&sql.NullString{}, // version
-		&sql.NullBool{},   // isValidated
-		&sql.NullString{}, // status
+func (*Machine) scanValues(columns []string) ([]interface{}, error) {
+	values := make([]interface{}, len(columns))
+	for i := range columns {
+		switch columns[i] {
+		case machine.FieldIsValidated:
+			values[i] = &sql.NullBool{}
+		case machine.FieldID:
+			values[i] = &sql.NullInt64{}
+		case machine.FieldMachineId, machine.FieldPassword, machine.FieldIpAddress, machine.FieldScenarios, machine.FieldVersion, machine.FieldStatus:
+			values[i] = &sql.NullString{}
+		case machine.FieldCreatedAt, machine.FieldUpdatedAt:
+			values[i] = &sql.NullTime{}
+		default:
+			return nil, fmt.Errorf("unexpected column %q for type Machine", columns[i])
+		}
 	}
+	return values, nil
 }
 
 // assignValues assigns the values that were returned from sql.Rows (after scanning)
 // to the Machine fields.
-func (m *Machine) assignValues(values ...interface{}) error {
-	if m, n := len(values), len(machine.Columns); m < n {
+func (m *Machine) assignValues(columns []string, values []interface{}) error {
+	if m, n := len(values), len(columns); m < n {
 		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
 	}
-	value, ok := values[0].(*sql.NullInt64)
-	if !ok {
-		return fmt.Errorf("unexpected type %T for field id", value)
-	}
-	m.ID = int(value.Int64)
-	values = values[1:]
-	if value, ok := values[0].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field created_at", values[0])
-	} else if value.Valid {
-		m.CreatedAt = value.Time
-	}
-	if value, ok := values[1].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field updated_at", values[1])
-	} else if value.Valid {
-		m.UpdatedAt = value.Time
-	}
-	if value, ok := values[2].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field machineId", values[2])
-	} else if value.Valid {
-		m.MachineId = value.String
-	}
-	if value, ok := values[3].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field password", values[3])
-	} else if value.Valid {
-		m.Password = value.String
-	}
-	if value, ok := values[4].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field ipAddress", values[4])
-	} else if value.Valid {
-		m.IpAddress = value.String
-	}
-	if value, ok := values[5].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field scenarios", values[5])
-	} else if value.Valid {
-		m.Scenarios = value.String
-	}
-	if value, ok := values[6].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field version", values[6])
-	} else if value.Valid {
-		m.Version = value.String
-	}
-	if value, ok := values[7].(*sql.NullBool); !ok {
-		return fmt.Errorf("unexpected type %T for field isValidated", values[7])
-	} else if value.Valid {
-		m.IsValidated = value.Bool
-	}
-	if value, ok := values[8].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field status", values[8])
-	} else if value.Valid {
-		m.Status = value.String
+	for i := range columns {
+		switch columns[i] {
+		case machine.FieldID:
+			value, ok := values[i].(*sql.NullInt64)
+			if !ok {
+				return fmt.Errorf("unexpected type %T for field id", value)
+			}
+			m.ID = int(value.Int64)
+		case machine.FieldCreatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field created_at", values[i])
+			} else if value.Valid {
+				m.CreatedAt = value.Time
+			}
+		case machine.FieldUpdatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field updated_at", values[i])
+			} else if value.Valid {
+				m.UpdatedAt = value.Time
+			}
+		case machine.FieldMachineId:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field machineId", values[i])
+			} else if value.Valid {
+				m.MachineId = value.String
+			}
+		case machine.FieldPassword:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field password", values[i])
+			} else if value.Valid {
+				m.Password = value.String
+			}
+		case machine.FieldIpAddress:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field ipAddress", values[i])
+			} else if value.Valid {
+				m.IpAddress = value.String
+			}
+		case machine.FieldScenarios:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field scenarios", values[i])
+			} else if value.Valid {
+				m.Scenarios = value.String
+			}
+		case machine.FieldVersion:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field version", values[i])
+			} else if value.Valid {
+				m.Version = value.String
+			}
+		case machine.FieldIsValidated:
+			if value, ok := values[i].(*sql.NullBool); !ok {
+				return fmt.Errorf("unexpected type %T for field isValidated", values[i])
+			} else if value.Valid {
+				m.IsValidated = value.Bool
+			}
+		case machine.FieldStatus:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field status", values[i])
+			} else if value.Valid {
+				m.Status = value.String
+			}
+		}
 	}
 	return nil
 }
 
-// QueryAlerts queries the alerts edge of the Machine.
+// QueryAlerts queries the "alerts" edge of the Machine entity.
 func (m *Machine) QueryAlerts() *AlertQuery {
 	return (&MachineClient{config: m.config}).QueryAlerts(m)
 }
 
 // Update returns a builder for updating this Machine.
-// Note that, you need to call Machine.Unwrap() before calling this method, if this Machine
+// Note that you need to call Machine.Unwrap() before calling this method if this Machine
 // was returned from a transaction, and the transaction was committed or rolled back.
 func (m *Machine) Update() *MachineUpdateOne {
 	return (&MachineClient{config: m.config}).UpdateOne(m)
 }
 
-// Unwrap unwraps the entity that was returned from a transaction after it was closed,
-// so that all next queries will be executed through the driver which created the transaction.
+// Unwrap unwraps the Machine entity that was returned from a transaction after it was closed,
+// so that all future queries will be executed through the driver which created the transaction.
 func (m *Machine) Unwrap() *Machine {
 	tx, ok := m.config.driver.(*txDriver)
 	if !ok {

+ 3 - 3
pkg/database/ent/machine/machine.go

@@ -69,12 +69,12 @@ func ValidColumn(column string) bool {
 }
 
 var (
-	// DefaultCreatedAt holds the default value on creation for the created_at field.
+	// DefaultCreatedAt holds the default value on creation for the "created_at" field.
 	DefaultCreatedAt func() time.Time
-	// DefaultUpdatedAt holds the default value on creation for the updated_at field.
+	// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
 	DefaultUpdatedAt func() time.Time
 	// ScenariosValidator is a validator for the "scenarios" field. It is called by the builders before save.
 	ScenariosValidator func(string) error
-	// DefaultIsValidated holds the default value on creation for the isValidated field.
+	// DefaultIsValidated holds the default value on creation for the "isValidated" field.
 	DefaultIsValidated bool
 )

+ 3 - 3
pkg/database/ent/machine/where.go

@@ -10,7 +10,7 @@ import (
 	"github.com/facebook/ent/dialect/sql/sqlgraph"
 )
 
-// ID filters vertices based on their identifier.
+// ID filters vertices based on their ID field.
 func ID(id int) predicate.Machine {
 	return predicate.Machine(func(s *sql.Selector) {
 		s.Where(sql.EQ(s.C(FieldID), id))
@@ -1058,7 +1058,7 @@ func HasAlertsWith(preds ...predicate.Alert) predicate.Machine {
 	})
 }
 
-// And groups list of predicates with the AND operator between them.
+// And groups predicates with the AND operator between them.
 func And(predicates ...predicate.Machine) predicate.Machine {
 	return predicate.Machine(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)
@@ -1069,7 +1069,7 @@ func And(predicates ...predicate.Machine) predicate.Machine {
 	})
 }
 
-// Or groups list of predicates with the OR operator between them.
+// Or groups predicates with the OR operator between them.
 func Or(predicates ...predicate.Machine) predicate.Machine {
 	return predicate.Machine(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)

+ 19 - 19
pkg/database/ent/machine_create.go

@@ -21,13 +21,13 @@ type MachineCreate struct {
 	hooks    []Hook
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (mc *MachineCreate) SetCreatedAt(t time.Time) *MachineCreate {
 	mc.mutation.SetCreatedAt(t)
 	return mc
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (mc *MachineCreate) SetNillableCreatedAt(t *time.Time) *MachineCreate {
 	if t != nil {
 		mc.SetCreatedAt(*t)
@@ -35,13 +35,13 @@ func (mc *MachineCreate) SetNillableCreatedAt(t *time.Time) *MachineCreate {
 	return mc
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (mc *MachineCreate) SetUpdatedAt(t time.Time) *MachineCreate {
 	mc.mutation.SetUpdatedAt(t)
 	return mc
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (mc *MachineCreate) SetNillableUpdatedAt(t *time.Time) *MachineCreate {
 	if t != nil {
 		mc.SetUpdatedAt(*t)
@@ -49,31 +49,31 @@ func (mc *MachineCreate) SetNillableUpdatedAt(t *time.Time) *MachineCreate {
 	return mc
 }
 
-// SetMachineId sets the machineId field.
+// SetMachineId sets the "machineId" field.
 func (mc *MachineCreate) SetMachineId(s string) *MachineCreate {
 	mc.mutation.SetMachineId(s)
 	return mc
 }
 
-// SetPassword sets the password field.
+// SetPassword sets the "password" field.
 func (mc *MachineCreate) SetPassword(s string) *MachineCreate {
 	mc.mutation.SetPassword(s)
 	return mc
 }
 
-// SetIpAddress sets the ipAddress field.
+// SetIpAddress sets the "ipAddress" field.
 func (mc *MachineCreate) SetIpAddress(s string) *MachineCreate {
 	mc.mutation.SetIpAddress(s)
 	return mc
 }
 
-// SetScenarios sets the scenarios field.
+// SetScenarios sets the "scenarios" field.
 func (mc *MachineCreate) SetScenarios(s string) *MachineCreate {
 	mc.mutation.SetScenarios(s)
 	return mc
 }
 
-// SetNillableScenarios sets the scenarios field if the given value is not nil.
+// SetNillableScenarios sets the "scenarios" field if the given value is not nil.
 func (mc *MachineCreate) SetNillableScenarios(s *string) *MachineCreate {
 	if s != nil {
 		mc.SetScenarios(*s)
@@ -81,13 +81,13 @@ func (mc *MachineCreate) SetNillableScenarios(s *string) *MachineCreate {
 	return mc
 }
 
-// SetVersion sets the version field.
+// SetVersion sets the "version" field.
 func (mc *MachineCreate) SetVersion(s string) *MachineCreate {
 	mc.mutation.SetVersion(s)
 	return mc
 }
 
-// SetNillableVersion sets the version field if the given value is not nil.
+// SetNillableVersion sets the "version" field if the given value is not nil.
 func (mc *MachineCreate) SetNillableVersion(s *string) *MachineCreate {
 	if s != nil {
 		mc.SetVersion(*s)
@@ -95,13 +95,13 @@ func (mc *MachineCreate) SetNillableVersion(s *string) *MachineCreate {
 	return mc
 }
 
-// SetIsValidated sets the isValidated field.
+// SetIsValidated sets the "isValidated" field.
 func (mc *MachineCreate) SetIsValidated(b bool) *MachineCreate {
 	mc.mutation.SetIsValidated(b)
 	return mc
 }
 
-// SetNillableIsValidated sets the isValidated field if the given value is not nil.
+// SetNillableIsValidated sets the "isValidated" field if the given value is not nil.
 func (mc *MachineCreate) SetNillableIsValidated(b *bool) *MachineCreate {
 	if b != nil {
 		mc.SetIsValidated(*b)
@@ -109,13 +109,13 @@ func (mc *MachineCreate) SetNillableIsValidated(b *bool) *MachineCreate {
 	return mc
 }
 
-// SetStatus sets the status field.
+// SetStatus sets the "status" field.
 func (mc *MachineCreate) SetStatus(s string) *MachineCreate {
 	mc.mutation.SetStatus(s)
 	return mc
 }
 
-// SetNillableStatus sets the status field if the given value is not nil.
+// SetNillableStatus sets the "status" field if the given value is not nil.
 func (mc *MachineCreate) SetNillableStatus(s *string) *MachineCreate {
 	if s != nil {
 		mc.SetStatus(*s)
@@ -123,13 +123,13 @@ func (mc *MachineCreate) SetNillableStatus(s *string) *MachineCreate {
 	return mc
 }
 
-// AddAlertIDs adds the alerts edge to Alert by ids.
+// AddAlertIDs adds the "alerts" edge to the Alert entity by IDs.
 func (mc *MachineCreate) AddAlertIDs(ids ...int) *MachineCreate {
 	mc.mutation.AddAlertIDs(ids...)
 	return mc
 }
 
-// AddAlerts adds the alerts edges to Alert.
+// AddAlerts adds the "alerts" edges to the Alert entity.
 func (mc *MachineCreate) AddAlerts(a ...*Alert) *MachineCreate {
 	ids := make([]int, len(a))
 	for i := range a {
@@ -350,7 +350,7 @@ func (mc *MachineCreate) createSpec() (*Machine, *sqlgraph.CreateSpec) {
 	return _node, _spec
 }
 
-// MachineCreateBulk is the builder for creating a bulk of Machine entities.
+// MachineCreateBulk is the builder for creating many Machine entities in bulk.
 type MachineCreateBulk struct {
 	config
 	builders []*MachineCreate
@@ -408,7 +408,7 @@ func (mcb *MachineCreateBulk) Save(ctx context.Context) ([]*Machine, error) {
 	return nodes, nil
 }
 
-// SaveX calls Save and panics if Save returns an error.
+// SaveX is like Save, but panics if an error occurs.
 func (mcb *MachineCreateBulk) SaveX(ctx context.Context) []*Machine {
 	v, err := mcb.Save(ctx)
 	if err != nil {

+ 5 - 6
pkg/database/ent/machine_delete.go

@@ -16,14 +16,13 @@ import (
 // MachineDelete is the builder for deleting a Machine entity.
 type MachineDelete struct {
 	config
-	hooks      []Hook
-	mutation   *MachineMutation
-	predicates []predicate.Machine
+	hooks    []Hook
+	mutation *MachineMutation
 }
 
-// Where adds a new predicate to the delete builder.
+// Where adds a new predicate to the MachineDelete builder.
 func (md *MachineDelete) Where(ps ...predicate.Machine) *MachineDelete {
-	md.predicates = append(md.predicates, ps...)
+	md.mutation.predicates = append(md.mutation.predicates, ps...)
 	return md
 }
 
@@ -75,7 +74,7 @@ func (md *MachineDelete) sqlExec(ctx context.Context) (int, error) {
 			},
 		},
 	}
-	if ps := md.predicates; len(ps) > 0 {
+	if ps := md.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)

+ 79 - 62
pkg/database/ent/machine_query.go

@@ -23,7 +23,7 @@ type MachineQuery struct {
 	limit      *int
 	offset     *int
 	order      []OrderFunc
-	unique     []string
+	fields     []string
 	predicates []predicate.Machine
 	// eager-loading edges.
 	withAlerts *AlertQuery
@@ -32,7 +32,7 @@ type MachineQuery struct {
 	path func(context.Context) (*sql.Selector, error)
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the MachineQuery builder.
 func (mq *MachineQuery) Where(ps ...predicate.Machine) *MachineQuery {
 	mq.predicates = append(mq.predicates, ps...)
 	return mq
@@ -56,7 +56,7 @@ func (mq *MachineQuery) Order(o ...OrderFunc) *MachineQuery {
 	return mq
 }
 
-// QueryAlerts chains the current query on the alerts edge.
+// QueryAlerts chains the current query on the "alerts" edge.
 func (mq *MachineQuery) QueryAlerts() *AlertQuery {
 	query := &AlertQuery{config: mq.config}
 	query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
@@ -78,7 +78,8 @@ func (mq *MachineQuery) QueryAlerts() *AlertQuery {
 	return query
 }
 
-// First returns the first Machine entity in the query. Returns *NotFoundError when no machine was found.
+// First returns the first Machine entity from the query.
+// Returns a *NotFoundError when no Machine was found.
 func (mq *MachineQuery) First(ctx context.Context) (*Machine, error) {
 	nodes, err := mq.Limit(1).All(ctx)
 	if err != nil {
@@ -99,7 +100,8 @@ func (mq *MachineQuery) FirstX(ctx context.Context) *Machine {
 	return node
 }
 
-// FirstID returns the first Machine id in the query. Returns *NotFoundError when no id was found.
+// FirstID returns the first Machine ID from the query.
+// Returns a *NotFoundError when no Machine ID was found.
 func (mq *MachineQuery) FirstID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = mq.Limit(1).IDs(ctx); err != nil {
@@ -112,8 +114,8 @@ func (mq *MachineQuery) FirstID(ctx context.Context) (id int, err error) {
 	return ids[0], nil
 }
 
-// FirstXID is like FirstID, but panics if an error occurs.
-func (mq *MachineQuery) FirstXID(ctx context.Context) int {
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (mq *MachineQuery) FirstIDX(ctx context.Context) int {
 	id, err := mq.FirstID(ctx)
 	if err != nil && !IsNotFound(err) {
 		panic(err)
@@ -121,7 +123,9 @@ func (mq *MachineQuery) FirstXID(ctx context.Context) int {
 	return id
 }
 
-// Only returns the only Machine entity in the query, returns an error if not exactly one entity was returned.
+// Only returns a single Machine entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when exactly one Machine entity is not found.
+// Returns a *NotFoundError when no Machine entities are found.
 func (mq *MachineQuery) Only(ctx context.Context) (*Machine, error) {
 	nodes, err := mq.Limit(2).All(ctx)
 	if err != nil {
@@ -146,7 +150,9 @@ func (mq *MachineQuery) OnlyX(ctx context.Context) *Machine {
 	return node
 }
 
-// OnlyID returns the only Machine id in the query, returns an error if not exactly one id was returned.
+// OnlyID is like Only, but returns the only Machine ID in the query.
+// Returns a *NotSingularError when exactly one Machine ID is not found.
+// Returns a *NotFoundError when no entities are found.
 func (mq *MachineQuery) OnlyID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = mq.Limit(2).IDs(ctx); err != nil {
@@ -189,7 +195,7 @@ func (mq *MachineQuery) AllX(ctx context.Context) []*Machine {
 	return nodes
 }
 
-// IDs executes the query and returns a list of Machine ids.
+// IDs executes the query and returns a list of Machine IDs.
 func (mq *MachineQuery) IDs(ctx context.Context) ([]int, error) {
 	var ids []int
 	if err := mq.Select(machine.FieldID).Scan(ctx, &ids); err != nil {
@@ -241,24 +247,27 @@ func (mq *MachineQuery) ExistX(ctx context.Context) bool {
 	return exist
 }
 
-// Clone returns a duplicate of the query builder, including all associated steps. It can be
+// Clone returns a duplicate of the MachineQuery builder, including all associated steps. It can be
 // used to prepare common query builders and use them differently after the clone is made.
 func (mq *MachineQuery) Clone() *MachineQuery {
+	if mq == nil {
+		return nil
+	}
 	return &MachineQuery{
 		config:     mq.config,
 		limit:      mq.limit,
 		offset:     mq.offset,
 		order:      append([]OrderFunc{}, mq.order...),
-		unique:     append([]string{}, mq.unique...),
 		predicates: append([]predicate.Machine{}, mq.predicates...),
+		withAlerts: mq.withAlerts.Clone(),
 		// clone intermediate query.
 		sql:  mq.sql.Clone(),
 		path: mq.path,
 	}
 }
 
-//  WithAlerts tells the query-builder to eager-loads the nodes that are connected to
-// the "alerts" edge. The optional arguments used to configure the query builder of the edge.
+// WithAlerts tells the query-builder to eager-load the nodes that are connected to
+// the "alerts" edge. The optional arguments are used to configure the query builder of the edge.
 func (mq *MachineQuery) WithAlerts(opts ...func(*AlertQuery)) *MachineQuery {
 	query := &AlertQuery{config: mq.config}
 	for _, opt := range opts {
@@ -268,7 +277,7 @@ func (mq *MachineQuery) WithAlerts(opts ...func(*AlertQuery)) *MachineQuery {
 	return mq
 }
 
-// GroupBy used to group vertices by one or more fields/columns.
+// GroupBy is used to group vertices by one or more fields/columns.
 // It is often used with aggregate functions, like: count, max, mean, min, sum.
 //
 // Example:
@@ -295,7 +304,8 @@ func (mq *MachineQuery) GroupBy(field string, fields ...string) *MachineGroupBy
 	return group
 }
 
-// Select one or more fields from the given query.
+// Select allows the selection one or more fields/columns for the given query,
+// instead of selecting all fields in the entity.
 //
 // Example:
 //
@@ -308,18 +318,16 @@ func (mq *MachineQuery) GroupBy(field string, fields ...string) *MachineGroupBy
 //		Scan(ctx, &v)
 //
 func (mq *MachineQuery) Select(field string, fields ...string) *MachineSelect {
-	selector := &MachineSelect{config: mq.config}
-	selector.fields = append([]string{field}, fields...)
-	selector.path = func(ctx context.Context) (prev *sql.Selector, err error) {
-		if err := mq.prepareQuery(ctx); err != nil {
-			return nil, err
-		}
-		return mq.sqlQuery(), nil
-	}
-	return selector
+	mq.fields = append([]string{field}, fields...)
+	return &MachineSelect{MachineQuery: mq}
 }
 
 func (mq *MachineQuery) prepareQuery(ctx context.Context) error {
+	for _, f := range mq.fields {
+		if !machine.ValidColumn(f) {
+			return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+		}
+	}
 	if mq.path != nil {
 		prev, err := mq.path(ctx)
 		if err != nil {
@@ -338,19 +346,18 @@ func (mq *MachineQuery) sqlAll(ctx context.Context) ([]*Machine, error) {
 			mq.withAlerts != nil,
 		}
 	)
-	_spec.ScanValues = func() []interface{} {
+	_spec.ScanValues = func(columns []string) ([]interface{}, error) {
 		node := &Machine{config: mq.config}
 		nodes = append(nodes, node)
-		values := node.scanValues()
-		return values
+		return node.scanValues(columns)
 	}
-	_spec.Assign = func(values ...interface{}) error {
+	_spec.Assign = func(columns []string, values []interface{}) error {
 		if len(nodes) == 0 {
 			return fmt.Errorf("ent: Assign called without calling ScanValues")
 		}
 		node := nodes[len(nodes)-1]
 		node.Edges.loadedTypes = loadedTypes
-		return node.assignValues(values...)
+		return node.assignValues(columns, values)
 	}
 	if err := sqlgraph.QueryNodes(ctx, mq.driver, _spec); err != nil {
 		return nil, err
@@ -365,6 +372,7 @@ func (mq *MachineQuery) sqlAll(ctx context.Context) ([]*Machine, error) {
 		for i := range nodes {
 			fks = append(fks, nodes[i].ID)
 			nodeids[nodes[i].ID] = nodes[i]
+			nodes[i].Edges.Alerts = []*Alert{}
 		}
 		query.withFKs = true
 		query.Where(predicate.Alert(func(s *sql.Selector) {
@@ -416,6 +424,15 @@ func (mq *MachineQuery) querySpec() *sqlgraph.QuerySpec {
 		From:   mq.sql,
 		Unique: true,
 	}
+	if fields := mq.fields; len(fields) > 0 {
+		_spec.Node.Columns = make([]string, 0, len(fields))
+		_spec.Node.Columns = append(_spec.Node.Columns, machine.FieldID)
+		for i := range fields {
+			if fields[i] != machine.FieldID {
+				_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+			}
+		}
+	}
 	if ps := mq.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
@@ -464,7 +481,7 @@ func (mq *MachineQuery) sqlQuery() *sql.Selector {
 	return selector
 }
 
-// MachineGroupBy is the builder for group-by Machine entities.
+// MachineGroupBy is the group-by builder for Machine entities.
 type MachineGroupBy struct {
 	config
 	fields []string
@@ -480,7 +497,7 @@ func (mgb *MachineGroupBy) Aggregate(fns ...AggregateFunc) *MachineGroupBy {
 	return mgb
 }
 
-// Scan applies the group-by query and scan the result into the given value.
+// Scan applies the group-by query and scans the result into the given value.
 func (mgb *MachineGroupBy) Scan(ctx context.Context, v interface{}) error {
 	query, err := mgb.path(ctx)
 	if err != nil {
@@ -497,7 +514,8 @@ func (mgb *MachineGroupBy) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from group-by. It is only allowed when querying group-by with one field.
+// Strings returns list of strings from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MachineGroupBy) Strings(ctx context.Context) ([]string, error) {
 	if len(mgb.fields) > 1 {
 		return nil, errors.New("ent: MachineGroupBy.Strings is not achievable when grouping more than 1 field")
@@ -518,7 +536,8 @@ func (mgb *MachineGroupBy) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from group-by. It is only allowed when querying group-by with one field.
+// String returns a single string from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MachineGroupBy) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = mgb.Strings(ctx); err != nil {
@@ -544,7 +563,8 @@ func (mgb *MachineGroupBy) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from group-by. It is only allowed when querying group-by with one field.
+// Ints returns list of ints from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MachineGroupBy) Ints(ctx context.Context) ([]int, error) {
 	if len(mgb.fields) > 1 {
 		return nil, errors.New("ent: MachineGroupBy.Ints is not achievable when grouping more than 1 field")
@@ -565,7 +585,8 @@ func (mgb *MachineGroupBy) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from group-by. It is only allowed when querying group-by with one field.
+// Int returns a single int from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MachineGroupBy) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = mgb.Ints(ctx); err != nil {
@@ -591,7 +612,8 @@ func (mgb *MachineGroupBy) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from group-by. It is only allowed when querying group-by with one field.
+// Float64s returns list of float64s from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MachineGroupBy) Float64s(ctx context.Context) ([]float64, error) {
 	if len(mgb.fields) > 1 {
 		return nil, errors.New("ent: MachineGroupBy.Float64s is not achievable when grouping more than 1 field")
@@ -612,7 +634,8 @@ func (mgb *MachineGroupBy) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from group-by. It is only allowed when querying group-by with one field.
+// Float64 returns a single float64 from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MachineGroupBy) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = mgb.Float64s(ctx); err != nil {
@@ -638,7 +661,8 @@ func (mgb *MachineGroupBy) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from group-by. It is only allowed when querying group-by with one field.
+// Bools returns list of bools from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MachineGroupBy) Bools(ctx context.Context) ([]bool, error) {
 	if len(mgb.fields) > 1 {
 		return nil, errors.New("ent: MachineGroupBy.Bools is not achievable when grouping more than 1 field")
@@ -659,7 +683,8 @@ func (mgb *MachineGroupBy) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from group-by. It is only allowed when querying group-by with one field.
+// Bool returns a single bool from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MachineGroupBy) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = mgb.Bools(ctx); err != nil {
@@ -714,22 +739,19 @@ func (mgb *MachineGroupBy) sqlQuery() *sql.Selector {
 	return selector.Select(columns...).GroupBy(mgb.fields...)
 }
 
-// MachineSelect is the builder for select fields of Machine entities.
+// MachineSelect is the builder for selecting fields of Machine entities.
 type MachineSelect struct {
-	config
-	fields []string
+	*MachineQuery
 	// intermediate query (i.e. traversal path).
-	sql  *sql.Selector
-	path func(context.Context) (*sql.Selector, error)
+	sql *sql.Selector
 }
 
-// Scan applies the selector query and scan the result into the given value.
+// Scan applies the selector query and scans the result into the given value.
 func (ms *MachineSelect) Scan(ctx context.Context, v interface{}) error {
-	query, err := ms.path(ctx)
-	if err != nil {
+	if err := ms.prepareQuery(ctx); err != nil {
 		return err
 	}
-	ms.sql = query
+	ms.sql = ms.MachineQuery.sqlQuery()
 	return ms.sqlScan(ctx, v)
 }
 
@@ -740,7 +762,7 @@ func (ms *MachineSelect) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from selector. It is only allowed when selecting one field.
+// Strings returns list of strings from a selector. It is only allowed when selecting one field.
 func (ms *MachineSelect) Strings(ctx context.Context) ([]string, error) {
 	if len(ms.fields) > 1 {
 		return nil, errors.New("ent: MachineSelect.Strings is not achievable when selecting more than 1 field")
@@ -761,7 +783,7 @@ func (ms *MachineSelect) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from selector. It is only allowed when selecting one field.
+// String returns a single string from a selector. It is only allowed when selecting one field.
 func (ms *MachineSelect) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = ms.Strings(ctx); err != nil {
@@ -787,7 +809,7 @@ func (ms *MachineSelect) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from selector. It is only allowed when selecting one field.
+// Ints returns list of ints from a selector. It is only allowed when selecting one field.
 func (ms *MachineSelect) Ints(ctx context.Context) ([]int, error) {
 	if len(ms.fields) > 1 {
 		return nil, errors.New("ent: MachineSelect.Ints is not achievable when selecting more than 1 field")
@@ -808,7 +830,7 @@ func (ms *MachineSelect) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from selector. It is only allowed when selecting one field.
+// Int returns a single int from a selector. It is only allowed when selecting one field.
 func (ms *MachineSelect) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = ms.Ints(ctx); err != nil {
@@ -834,7 +856,7 @@ func (ms *MachineSelect) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from selector. It is only allowed when selecting one field.
+// Float64s returns list of float64s from a selector. It is only allowed when selecting one field.
 func (ms *MachineSelect) Float64s(ctx context.Context) ([]float64, error) {
 	if len(ms.fields) > 1 {
 		return nil, errors.New("ent: MachineSelect.Float64s is not achievable when selecting more than 1 field")
@@ -855,7 +877,7 @@ func (ms *MachineSelect) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from selector. It is only allowed when selecting one field.
+// Float64 returns a single float64 from a selector. It is only allowed when selecting one field.
 func (ms *MachineSelect) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = ms.Float64s(ctx); err != nil {
@@ -881,7 +903,7 @@ func (ms *MachineSelect) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from selector. It is only allowed when selecting one field.
+// Bools returns list of bools from a selector. It is only allowed when selecting one field.
 func (ms *MachineSelect) Bools(ctx context.Context) ([]bool, error) {
 	if len(ms.fields) > 1 {
 		return nil, errors.New("ent: MachineSelect.Bools is not achievable when selecting more than 1 field")
@@ -902,7 +924,7 @@ func (ms *MachineSelect) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from selector. It is only allowed when selecting one field.
+// Bool returns a single bool from a selector. It is only allowed when selecting one field.
 func (ms *MachineSelect) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = ms.Bools(ctx); err != nil {
@@ -929,11 +951,6 @@ func (ms *MachineSelect) BoolX(ctx context.Context) bool {
 }
 
 func (ms *MachineSelect) sqlScan(ctx context.Context, v interface{}) error {
-	for _, f := range ms.fields {
-		if !machine.ValidColumn(f) {
-			return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for selection", f)}
-		}
-	}
 	rows := &sql.Rows{}
 	query, args := ms.sqlQuery().Query()
 	if err := ms.driver.Query(ctx, query, args, rows); err != nil {

+ 54 - 55
pkg/database/ent/machine_update.go

@@ -18,24 +18,23 @@ import (
 // MachineUpdate is the builder for updating Machine entities.
 type MachineUpdate struct {
 	config
-	hooks      []Hook
-	mutation   *MachineMutation
-	predicates []predicate.Machine
+	hooks    []Hook
+	mutation *MachineMutation
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the MachineUpdate builder.
 func (mu *MachineUpdate) Where(ps ...predicate.Machine) *MachineUpdate {
-	mu.predicates = append(mu.predicates, ps...)
+	mu.mutation.predicates = append(mu.mutation.predicates, ps...)
 	return mu
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (mu *MachineUpdate) SetCreatedAt(t time.Time) *MachineUpdate {
 	mu.mutation.SetCreatedAt(t)
 	return mu
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (mu *MachineUpdate) SetNillableCreatedAt(t *time.Time) *MachineUpdate {
 	if t != nil {
 		mu.SetCreatedAt(*t)
@@ -43,13 +42,13 @@ func (mu *MachineUpdate) SetNillableCreatedAt(t *time.Time) *MachineUpdate {
 	return mu
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (mu *MachineUpdate) SetUpdatedAt(t time.Time) *MachineUpdate {
 	mu.mutation.SetUpdatedAt(t)
 	return mu
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (mu *MachineUpdate) SetNillableUpdatedAt(t *time.Time) *MachineUpdate {
 	if t != nil {
 		mu.SetUpdatedAt(*t)
@@ -57,31 +56,31 @@ func (mu *MachineUpdate) SetNillableUpdatedAt(t *time.Time) *MachineUpdate {
 	return mu
 }
 
-// SetMachineId sets the machineId field.
+// SetMachineId sets the "machineId" field.
 func (mu *MachineUpdate) SetMachineId(s string) *MachineUpdate {
 	mu.mutation.SetMachineId(s)
 	return mu
 }
 
-// SetPassword sets the password field.
+// SetPassword sets the "password" field.
 func (mu *MachineUpdate) SetPassword(s string) *MachineUpdate {
 	mu.mutation.SetPassword(s)
 	return mu
 }
 
-// SetIpAddress sets the ipAddress field.
+// SetIpAddress sets the "ipAddress" field.
 func (mu *MachineUpdate) SetIpAddress(s string) *MachineUpdate {
 	mu.mutation.SetIpAddress(s)
 	return mu
 }
 
-// SetScenarios sets the scenarios field.
+// SetScenarios sets the "scenarios" field.
 func (mu *MachineUpdate) SetScenarios(s string) *MachineUpdate {
 	mu.mutation.SetScenarios(s)
 	return mu
 }
 
-// SetNillableScenarios sets the scenarios field if the given value is not nil.
+// SetNillableScenarios sets the "scenarios" field if the given value is not nil.
 func (mu *MachineUpdate) SetNillableScenarios(s *string) *MachineUpdate {
 	if s != nil {
 		mu.SetScenarios(*s)
@@ -89,19 +88,19 @@ func (mu *MachineUpdate) SetNillableScenarios(s *string) *MachineUpdate {
 	return mu
 }
 
-// ClearScenarios clears the value of scenarios.
+// ClearScenarios clears the value of the "scenarios" field.
 func (mu *MachineUpdate) ClearScenarios() *MachineUpdate {
 	mu.mutation.ClearScenarios()
 	return mu
 }
 
-// SetVersion sets the version field.
+// SetVersion sets the "version" field.
 func (mu *MachineUpdate) SetVersion(s string) *MachineUpdate {
 	mu.mutation.SetVersion(s)
 	return mu
 }
 
-// SetNillableVersion sets the version field if the given value is not nil.
+// SetNillableVersion sets the "version" field if the given value is not nil.
 func (mu *MachineUpdate) SetNillableVersion(s *string) *MachineUpdate {
 	if s != nil {
 		mu.SetVersion(*s)
@@ -109,19 +108,19 @@ func (mu *MachineUpdate) SetNillableVersion(s *string) *MachineUpdate {
 	return mu
 }
 
-// ClearVersion clears the value of version.
+// ClearVersion clears the value of the "version" field.
 func (mu *MachineUpdate) ClearVersion() *MachineUpdate {
 	mu.mutation.ClearVersion()
 	return mu
 }
 
-// SetIsValidated sets the isValidated field.
+// SetIsValidated sets the "isValidated" field.
 func (mu *MachineUpdate) SetIsValidated(b bool) *MachineUpdate {
 	mu.mutation.SetIsValidated(b)
 	return mu
 }
 
-// SetNillableIsValidated sets the isValidated field if the given value is not nil.
+// SetNillableIsValidated sets the "isValidated" field if the given value is not nil.
 func (mu *MachineUpdate) SetNillableIsValidated(b *bool) *MachineUpdate {
 	if b != nil {
 		mu.SetIsValidated(*b)
@@ -129,13 +128,13 @@ func (mu *MachineUpdate) SetNillableIsValidated(b *bool) *MachineUpdate {
 	return mu
 }
 
-// SetStatus sets the status field.
+// SetStatus sets the "status" field.
 func (mu *MachineUpdate) SetStatus(s string) *MachineUpdate {
 	mu.mutation.SetStatus(s)
 	return mu
 }
 
-// SetNillableStatus sets the status field if the given value is not nil.
+// SetNillableStatus sets the "status" field if the given value is not nil.
 func (mu *MachineUpdate) SetNillableStatus(s *string) *MachineUpdate {
 	if s != nil {
 		mu.SetStatus(*s)
@@ -143,19 +142,19 @@ func (mu *MachineUpdate) SetNillableStatus(s *string) *MachineUpdate {
 	return mu
 }
 
-// ClearStatus clears the value of status.
+// ClearStatus clears the value of the "status" field.
 func (mu *MachineUpdate) ClearStatus() *MachineUpdate {
 	mu.mutation.ClearStatus()
 	return mu
 }
 
-// AddAlertIDs adds the alerts edge to Alert by ids.
+// AddAlertIDs adds the "alerts" edge to the Alert entity by IDs.
 func (mu *MachineUpdate) AddAlertIDs(ids ...int) *MachineUpdate {
 	mu.mutation.AddAlertIDs(ids...)
 	return mu
 }
 
-// AddAlerts adds the alerts edges to Alert.
+// AddAlerts adds the "alerts" edges to the Alert entity.
 func (mu *MachineUpdate) AddAlerts(a ...*Alert) *MachineUpdate {
 	ids := make([]int, len(a))
 	for i := range a {
@@ -169,19 +168,19 @@ func (mu *MachineUpdate) Mutation() *MachineMutation {
 	return mu.mutation
 }
 
-// ClearAlerts clears all "alerts" edges to type Alert.
+// ClearAlerts clears all "alerts" edges to the Alert entity.
 func (mu *MachineUpdate) ClearAlerts() *MachineUpdate {
 	mu.mutation.ClearAlerts()
 	return mu
 }
 
-// RemoveAlertIDs removes the alerts edge to Alert by ids.
+// RemoveAlertIDs removes the "alerts" edge to Alert entities by IDs.
 func (mu *MachineUpdate) RemoveAlertIDs(ids ...int) *MachineUpdate {
 	mu.mutation.RemoveAlertIDs(ids...)
 	return mu
 }
 
-// RemoveAlerts removes alerts edges to Alert.
+// RemoveAlerts removes "alerts" edges to Alert entities.
 func (mu *MachineUpdate) RemoveAlerts(a ...*Alert) *MachineUpdate {
 	ids := make([]int, len(a))
 	for i := range a {
@@ -190,7 +189,7 @@ func (mu *MachineUpdate) RemoveAlerts(a ...*Alert) *MachineUpdate {
 	return mu.RemoveAlertIDs(ids...)
 }
 
-// Save executes the query and returns the number of rows/vertices matched by this operation.
+// Save executes the query and returns the number of nodes affected by the update operation.
 func (mu *MachineUpdate) Save(ctx context.Context) (int, error) {
 	var (
 		err      error
@@ -268,7 +267,7 @@ func (mu *MachineUpdate) sqlSave(ctx context.Context) (n int, err error) {
 			},
 		},
 	}
-	if ps := mu.predicates; len(ps) > 0 {
+	if ps := mu.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)
@@ -428,13 +427,13 @@ type MachineUpdateOne struct {
 	mutation *MachineMutation
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (muo *MachineUpdateOne) SetCreatedAt(t time.Time) *MachineUpdateOne {
 	muo.mutation.SetCreatedAt(t)
 	return muo
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (muo *MachineUpdateOne) SetNillableCreatedAt(t *time.Time) *MachineUpdateOne {
 	if t != nil {
 		muo.SetCreatedAt(*t)
@@ -442,13 +441,13 @@ func (muo *MachineUpdateOne) SetNillableCreatedAt(t *time.Time) *MachineUpdateOn
 	return muo
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (muo *MachineUpdateOne) SetUpdatedAt(t time.Time) *MachineUpdateOne {
 	muo.mutation.SetUpdatedAt(t)
 	return muo
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (muo *MachineUpdateOne) SetNillableUpdatedAt(t *time.Time) *MachineUpdateOne {
 	if t != nil {
 		muo.SetUpdatedAt(*t)
@@ -456,31 +455,31 @@ func (muo *MachineUpdateOne) SetNillableUpdatedAt(t *time.Time) *MachineUpdateOn
 	return muo
 }
 
-// SetMachineId sets the machineId field.
+// SetMachineId sets the "machineId" field.
 func (muo *MachineUpdateOne) SetMachineId(s string) *MachineUpdateOne {
 	muo.mutation.SetMachineId(s)
 	return muo
 }
 
-// SetPassword sets the password field.
+// SetPassword sets the "password" field.
 func (muo *MachineUpdateOne) SetPassword(s string) *MachineUpdateOne {
 	muo.mutation.SetPassword(s)
 	return muo
 }
 
-// SetIpAddress sets the ipAddress field.
+// SetIpAddress sets the "ipAddress" field.
 func (muo *MachineUpdateOne) SetIpAddress(s string) *MachineUpdateOne {
 	muo.mutation.SetIpAddress(s)
 	return muo
 }
 
-// SetScenarios sets the scenarios field.
+// SetScenarios sets the "scenarios" field.
 func (muo *MachineUpdateOne) SetScenarios(s string) *MachineUpdateOne {
 	muo.mutation.SetScenarios(s)
 	return muo
 }
 
-// SetNillableScenarios sets the scenarios field if the given value is not nil.
+// SetNillableScenarios sets the "scenarios" field if the given value is not nil.
 func (muo *MachineUpdateOne) SetNillableScenarios(s *string) *MachineUpdateOne {
 	if s != nil {
 		muo.SetScenarios(*s)
@@ -488,19 +487,19 @@ func (muo *MachineUpdateOne) SetNillableScenarios(s *string) *MachineUpdateOne {
 	return muo
 }
 
-// ClearScenarios clears the value of scenarios.
+// ClearScenarios clears the value of the "scenarios" field.
 func (muo *MachineUpdateOne) ClearScenarios() *MachineUpdateOne {
 	muo.mutation.ClearScenarios()
 	return muo
 }
 
-// SetVersion sets the version field.
+// SetVersion sets the "version" field.
 func (muo *MachineUpdateOne) SetVersion(s string) *MachineUpdateOne {
 	muo.mutation.SetVersion(s)
 	return muo
 }
 
-// SetNillableVersion sets the version field if the given value is not nil.
+// SetNillableVersion sets the "version" field if the given value is not nil.
 func (muo *MachineUpdateOne) SetNillableVersion(s *string) *MachineUpdateOne {
 	if s != nil {
 		muo.SetVersion(*s)
@@ -508,19 +507,19 @@ func (muo *MachineUpdateOne) SetNillableVersion(s *string) *MachineUpdateOne {
 	return muo
 }
 
-// ClearVersion clears the value of version.
+// ClearVersion clears the value of the "version" field.
 func (muo *MachineUpdateOne) ClearVersion() *MachineUpdateOne {
 	muo.mutation.ClearVersion()
 	return muo
 }
 
-// SetIsValidated sets the isValidated field.
+// SetIsValidated sets the "isValidated" field.
 func (muo *MachineUpdateOne) SetIsValidated(b bool) *MachineUpdateOne {
 	muo.mutation.SetIsValidated(b)
 	return muo
 }
 
-// SetNillableIsValidated sets the isValidated field if the given value is not nil.
+// SetNillableIsValidated sets the "isValidated" field if the given value is not nil.
 func (muo *MachineUpdateOne) SetNillableIsValidated(b *bool) *MachineUpdateOne {
 	if b != nil {
 		muo.SetIsValidated(*b)
@@ -528,13 +527,13 @@ func (muo *MachineUpdateOne) SetNillableIsValidated(b *bool) *MachineUpdateOne {
 	return muo
 }
 
-// SetStatus sets the status field.
+// SetStatus sets the "status" field.
 func (muo *MachineUpdateOne) SetStatus(s string) *MachineUpdateOne {
 	muo.mutation.SetStatus(s)
 	return muo
 }
 
-// SetNillableStatus sets the status field if the given value is not nil.
+// SetNillableStatus sets the "status" field if the given value is not nil.
 func (muo *MachineUpdateOne) SetNillableStatus(s *string) *MachineUpdateOne {
 	if s != nil {
 		muo.SetStatus(*s)
@@ -542,19 +541,19 @@ func (muo *MachineUpdateOne) SetNillableStatus(s *string) *MachineUpdateOne {
 	return muo
 }
 
-// ClearStatus clears the value of status.
+// ClearStatus clears the value of the "status" field.
 func (muo *MachineUpdateOne) ClearStatus() *MachineUpdateOne {
 	muo.mutation.ClearStatus()
 	return muo
 }
 
-// AddAlertIDs adds the alerts edge to Alert by ids.
+// AddAlertIDs adds the "alerts" edge to the Alert entity by IDs.
 func (muo *MachineUpdateOne) AddAlertIDs(ids ...int) *MachineUpdateOne {
 	muo.mutation.AddAlertIDs(ids...)
 	return muo
 }
 
-// AddAlerts adds the alerts edges to Alert.
+// AddAlerts adds the "alerts" edges to the Alert entity.
 func (muo *MachineUpdateOne) AddAlerts(a ...*Alert) *MachineUpdateOne {
 	ids := make([]int, len(a))
 	for i := range a {
@@ -568,19 +567,19 @@ func (muo *MachineUpdateOne) Mutation() *MachineMutation {
 	return muo.mutation
 }
 
-// ClearAlerts clears all "alerts" edges to type Alert.
+// ClearAlerts clears all "alerts" edges to the Alert entity.
 func (muo *MachineUpdateOne) ClearAlerts() *MachineUpdateOne {
 	muo.mutation.ClearAlerts()
 	return muo
 }
 
-// RemoveAlertIDs removes the alerts edge to Alert by ids.
+// RemoveAlertIDs removes the "alerts" edge to Alert entities by IDs.
 func (muo *MachineUpdateOne) RemoveAlertIDs(ids ...int) *MachineUpdateOne {
 	muo.mutation.RemoveAlertIDs(ids...)
 	return muo
 }
 
-// RemoveAlerts removes alerts edges to Alert.
+// RemoveAlerts removes "alerts" edges to Alert entities.
 func (muo *MachineUpdateOne) RemoveAlerts(a ...*Alert) *MachineUpdateOne {
 	ids := make([]int, len(a))
 	for i := range a {
@@ -589,7 +588,7 @@ func (muo *MachineUpdateOne) RemoveAlerts(a ...*Alert) *MachineUpdateOne {
 	return muo.RemoveAlertIDs(ids...)
 }
 
-// Save executes the query and returns the updated entity.
+// Save executes the query and returns the updated Machine entity.
 func (muo *MachineUpdateOne) Save(ctx context.Context) (*Machine, error) {
 	var (
 		err  error
@@ -809,7 +808,7 @@ func (muo *MachineUpdateOne) sqlSave(ctx context.Context) (_node *Machine, err e
 	}
 	_node = &Machine{config: muo.config}
 	_spec.Assign = _node.assignValues
-	_spec.ScanValues = _node.scanValues()
+	_spec.ScanValues = _node.scanValues
 	if err = sqlgraph.UpdateNode(ctx, muo.driver, _spec); err != nil {
 		if _, ok := err.(*sqlgraph.NotFoundError); ok {
 			err = &NotFoundError{machine.Label}

+ 61 - 53
pkg/database/ent/meta.go

@@ -55,81 +55,89 @@ func (e MetaEdges) OwnerOrErr() (*Alert, error) {
 }
 
 // scanValues returns the types for scanning values from sql.Rows.
-func (*Meta) scanValues() []interface{} {
-	return []interface{}{
-		&sql.NullInt64{},  // id
-		&sql.NullTime{},   // created_at
-		&sql.NullTime{},   // updated_at
-		&sql.NullString{}, // key
-		&sql.NullString{}, // value
-	}
-}
-
-// fkValues returns the types for scanning foreign-keys values from sql.Rows.
-func (*Meta) fkValues() []interface{} {
-	return []interface{}{
-		&sql.NullInt64{}, // alert_metas
+func (*Meta) scanValues(columns []string) ([]interface{}, error) {
+	values := make([]interface{}, len(columns))
+	for i := range columns {
+		switch columns[i] {
+		case meta.FieldID:
+			values[i] = &sql.NullInt64{}
+		case meta.FieldKey, meta.FieldValue:
+			values[i] = &sql.NullString{}
+		case meta.FieldCreatedAt, meta.FieldUpdatedAt:
+			values[i] = &sql.NullTime{}
+		case meta.ForeignKeys[0]: // alert_metas
+			values[i] = &sql.NullInt64{}
+		default:
+			return nil, fmt.Errorf("unexpected column %q for type Meta", columns[i])
+		}
 	}
+	return values, nil
 }
 
 // assignValues assigns the values that were returned from sql.Rows (after scanning)
 // to the Meta fields.
-func (m *Meta) assignValues(values ...interface{}) error {
-	if m, n := len(values), len(meta.Columns); m < n {
+func (m *Meta) assignValues(columns []string, values []interface{}) error {
+	if m, n := len(values), len(columns); m < n {
 		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
 	}
-	value, ok := values[0].(*sql.NullInt64)
-	if !ok {
-		return fmt.Errorf("unexpected type %T for field id", value)
-	}
-	m.ID = int(value.Int64)
-	values = values[1:]
-	if value, ok := values[0].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field created_at", values[0])
-	} else if value.Valid {
-		m.CreatedAt = value.Time
-	}
-	if value, ok := values[1].(*sql.NullTime); !ok {
-		return fmt.Errorf("unexpected type %T for field updated_at", values[1])
-	} else if value.Valid {
-		m.UpdatedAt = value.Time
-	}
-	if value, ok := values[2].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field key", values[2])
-	} else if value.Valid {
-		m.Key = value.String
-	}
-	if value, ok := values[3].(*sql.NullString); !ok {
-		return fmt.Errorf("unexpected type %T for field value", values[3])
-	} else if value.Valid {
-		m.Value = value.String
-	}
-	values = values[4:]
-	if len(values) == len(meta.ForeignKeys) {
-		if value, ok := values[0].(*sql.NullInt64); !ok {
-			return fmt.Errorf("unexpected type %T for edge-field alert_metas", value)
-		} else if value.Valid {
-			m.alert_metas = new(int)
-			*m.alert_metas = int(value.Int64)
+	for i := range columns {
+		switch columns[i] {
+		case meta.FieldID:
+			value, ok := values[i].(*sql.NullInt64)
+			if !ok {
+				return fmt.Errorf("unexpected type %T for field id", value)
+			}
+			m.ID = int(value.Int64)
+		case meta.FieldCreatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field created_at", values[i])
+			} else if value.Valid {
+				m.CreatedAt = value.Time
+			}
+		case meta.FieldUpdatedAt:
+			if value, ok := values[i].(*sql.NullTime); !ok {
+				return fmt.Errorf("unexpected type %T for field updated_at", values[i])
+			} else if value.Valid {
+				m.UpdatedAt = value.Time
+			}
+		case meta.FieldKey:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field key", values[i])
+			} else if value.Valid {
+				m.Key = value.String
+			}
+		case meta.FieldValue:
+			if value, ok := values[i].(*sql.NullString); !ok {
+				return fmt.Errorf("unexpected type %T for field value", values[i])
+			} else if value.Valid {
+				m.Value = value.String
+			}
+		case meta.ForeignKeys[0]:
+			if value, ok := values[i].(*sql.NullInt64); !ok {
+				return fmt.Errorf("unexpected type %T for edge-field alert_metas", value)
+			} else if value.Valid {
+				m.alert_metas = new(int)
+				*m.alert_metas = int(value.Int64)
+			}
 		}
 	}
 	return nil
 }
 
-// QueryOwner queries the owner edge of the Meta.
+// QueryOwner queries the "owner" edge of the Meta entity.
 func (m *Meta) QueryOwner() *AlertQuery {
 	return (&MetaClient{config: m.config}).QueryOwner(m)
 }
 
 // Update returns a builder for updating this Meta.
-// Note that, you need to call Meta.Unwrap() before calling this method, if this Meta
+// Note that you need to call Meta.Unwrap() before calling this method if this Meta
 // was returned from a transaction, and the transaction was committed or rolled back.
 func (m *Meta) Update() *MetaUpdateOne {
 	return (&MetaClient{config: m.config}).UpdateOne(m)
 }
 
-// Unwrap unwraps the entity that was returned from a transaction after it was closed,
-// so that all next queries will be executed through the driver which created the transaction.
+// Unwrap unwraps the Meta entity that was returned from a transaction after it was closed,
+// so that all future queries will be executed through the driver which created the transaction.
 func (m *Meta) Unwrap() *Meta {
 	tx, ok := m.config.driver.(*txDriver)
 	if !ok {

+ 2 - 2
pkg/database/ent/meta/meta.go

@@ -64,9 +64,9 @@ func ValidColumn(column string) bool {
 }
 
 var (
-	// DefaultCreatedAt holds the default value on creation for the created_at field.
+	// DefaultCreatedAt holds the default value on creation for the "created_at" field.
 	DefaultCreatedAt func() time.Time
-	// DefaultUpdatedAt holds the default value on creation for the updated_at field.
+	// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
 	DefaultUpdatedAt func() time.Time
 	// ValueValidator is a validator for the "value" field. It is called by the builders before save.
 	ValueValidator func(string) error

+ 3 - 3
pkg/database/ent/meta/where.go

@@ -10,7 +10,7 @@ import (
 	"github.com/facebook/ent/dialect/sql/sqlgraph"
 )
 
-// ID filters vertices based on their identifier.
+// ID filters vertices based on their ID field.
 func ID(id int) predicate.Meta {
 	return predicate.Meta(func(s *sql.Selector) {
 		s.Where(sql.EQ(s.C(FieldID), id))
@@ -523,7 +523,7 @@ func HasOwnerWith(preds ...predicate.Alert) predicate.Meta {
 	})
 }
 
-// And groups list of predicates with the AND operator between them.
+// And groups predicates with the AND operator between them.
 func And(predicates ...predicate.Meta) predicate.Meta {
 	return predicate.Meta(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)
@@ -534,7 +534,7 @@ func And(predicates ...predicate.Meta) predicate.Meta {
 	})
 }
 
-// Or groups list of predicates with the OR operator between them.
+// Or groups predicates with the OR operator between them.
 func Or(predicates ...predicate.Meta) predicate.Meta {
 	return predicate.Meta(func(s *sql.Selector) {
 		s1 := s.Clone().SetP(nil)

+ 11 - 11
pkg/database/ent/meta_create.go

@@ -21,13 +21,13 @@ type MetaCreate struct {
 	hooks    []Hook
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (mc *MetaCreate) SetCreatedAt(t time.Time) *MetaCreate {
 	mc.mutation.SetCreatedAt(t)
 	return mc
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (mc *MetaCreate) SetNillableCreatedAt(t *time.Time) *MetaCreate {
 	if t != nil {
 		mc.SetCreatedAt(*t)
@@ -35,13 +35,13 @@ func (mc *MetaCreate) SetNillableCreatedAt(t *time.Time) *MetaCreate {
 	return mc
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (mc *MetaCreate) SetUpdatedAt(t time.Time) *MetaCreate {
 	mc.mutation.SetUpdatedAt(t)
 	return mc
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (mc *MetaCreate) SetNillableUpdatedAt(t *time.Time) *MetaCreate {
 	if t != nil {
 		mc.SetUpdatedAt(*t)
@@ -49,25 +49,25 @@ func (mc *MetaCreate) SetNillableUpdatedAt(t *time.Time) *MetaCreate {
 	return mc
 }
 
-// SetKey sets the key field.
+// SetKey sets the "key" field.
 func (mc *MetaCreate) SetKey(s string) *MetaCreate {
 	mc.mutation.SetKey(s)
 	return mc
 }
 
-// SetValue sets the value field.
+// SetValue sets the "value" field.
 func (mc *MetaCreate) SetValue(s string) *MetaCreate {
 	mc.mutation.SetValue(s)
 	return mc
 }
 
-// SetOwnerID sets the owner edge to Alert by id.
+// SetOwnerID sets the "owner" edge to the Alert entity by ID.
 func (mc *MetaCreate) SetOwnerID(id int) *MetaCreate {
 	mc.mutation.SetOwnerID(id)
 	return mc
 }
 
-// SetNillableOwnerID sets the owner edge to Alert by id if the given value is not nil.
+// SetNillableOwnerID sets the "owner" edge to the Alert entity by ID if the given value is not nil.
 func (mc *MetaCreate) SetNillableOwnerID(id *int) *MetaCreate {
 	if id != nil {
 		mc = mc.SetOwnerID(*id)
@@ -75,7 +75,7 @@ func (mc *MetaCreate) SetNillableOwnerID(id *int) *MetaCreate {
 	return mc
 }
 
-// SetOwner sets the owner edge to Alert.
+// SetOwner sets the "owner" edge to the Alert entity.
 func (mc *MetaCreate) SetOwner(a *Alert) *MetaCreate {
 	return mc.SetOwnerID(a.ID)
 }
@@ -242,7 +242,7 @@ func (mc *MetaCreate) createSpec() (*Meta, *sqlgraph.CreateSpec) {
 	return _node, _spec
 }
 
-// MetaCreateBulk is the builder for creating a bulk of Meta entities.
+// MetaCreateBulk is the builder for creating many Meta entities in bulk.
 type MetaCreateBulk struct {
 	config
 	builders []*MetaCreate
@@ -300,7 +300,7 @@ func (mcb *MetaCreateBulk) Save(ctx context.Context) ([]*Meta, error) {
 	return nodes, nil
 }
 
-// SaveX calls Save and panics if Save returns an error.
+// SaveX is like Save, but panics if an error occurs.
 func (mcb *MetaCreateBulk) SaveX(ctx context.Context) []*Meta {
 	v, err := mcb.Save(ctx)
 	if err != nil {

+ 5 - 6
pkg/database/ent/meta_delete.go

@@ -16,14 +16,13 @@ import (
 // MetaDelete is the builder for deleting a Meta entity.
 type MetaDelete struct {
 	config
-	hooks      []Hook
-	mutation   *MetaMutation
-	predicates []predicate.Meta
+	hooks    []Hook
+	mutation *MetaMutation
 }
 
-// Where adds a new predicate to the delete builder.
+// Where adds a new predicate to the MetaDelete builder.
 func (md *MetaDelete) Where(ps ...predicate.Meta) *MetaDelete {
-	md.predicates = append(md.predicates, ps...)
+	md.mutation.predicates = append(md.mutation.predicates, ps...)
 	return md
 }
 
@@ -75,7 +74,7 @@ func (md *MetaDelete) sqlExec(ctx context.Context) (int, error) {
 			},
 		},
 	}
-	if ps := md.predicates; len(ps) > 0 {
+	if ps := md.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)

+ 78 - 65
pkg/database/ent/meta_query.go

@@ -22,7 +22,7 @@ type MetaQuery struct {
 	limit      *int
 	offset     *int
 	order      []OrderFunc
-	unique     []string
+	fields     []string
 	predicates []predicate.Meta
 	// eager-loading edges.
 	withOwner *AlertQuery
@@ -32,7 +32,7 @@ type MetaQuery struct {
 	path func(context.Context) (*sql.Selector, error)
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the MetaQuery builder.
 func (mq *MetaQuery) Where(ps ...predicate.Meta) *MetaQuery {
 	mq.predicates = append(mq.predicates, ps...)
 	return mq
@@ -56,7 +56,7 @@ func (mq *MetaQuery) Order(o ...OrderFunc) *MetaQuery {
 	return mq
 }
 
-// QueryOwner chains the current query on the owner edge.
+// QueryOwner chains the current query on the "owner" edge.
 func (mq *MetaQuery) QueryOwner() *AlertQuery {
 	query := &AlertQuery{config: mq.config}
 	query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
@@ -78,7 +78,8 @@ func (mq *MetaQuery) QueryOwner() *AlertQuery {
 	return query
 }
 
-// First returns the first Meta entity in the query. Returns *NotFoundError when no meta was found.
+// First returns the first Meta entity from the query.
+// Returns a *NotFoundError when no Meta was found.
 func (mq *MetaQuery) First(ctx context.Context) (*Meta, error) {
 	nodes, err := mq.Limit(1).All(ctx)
 	if err != nil {
@@ -99,7 +100,8 @@ func (mq *MetaQuery) FirstX(ctx context.Context) *Meta {
 	return node
 }
 
-// FirstID returns the first Meta id in the query. Returns *NotFoundError when no id was found.
+// FirstID returns the first Meta ID from the query.
+// Returns a *NotFoundError when no Meta ID was found.
 func (mq *MetaQuery) FirstID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = mq.Limit(1).IDs(ctx); err != nil {
@@ -112,8 +114,8 @@ func (mq *MetaQuery) FirstID(ctx context.Context) (id int, err error) {
 	return ids[0], nil
 }
 
-// FirstXID is like FirstID, but panics if an error occurs.
-func (mq *MetaQuery) FirstXID(ctx context.Context) int {
+// FirstIDX is like FirstID, but panics if an error occurs.
+func (mq *MetaQuery) FirstIDX(ctx context.Context) int {
 	id, err := mq.FirstID(ctx)
 	if err != nil && !IsNotFound(err) {
 		panic(err)
@@ -121,7 +123,9 @@ func (mq *MetaQuery) FirstXID(ctx context.Context) int {
 	return id
 }
 
-// Only returns the only Meta entity in the query, returns an error if not exactly one entity was returned.
+// Only returns a single Meta entity found by the query, ensuring it only returns one.
+// Returns a *NotSingularError when exactly one Meta entity is not found.
+// Returns a *NotFoundError when no Meta entities are found.
 func (mq *MetaQuery) Only(ctx context.Context) (*Meta, error) {
 	nodes, err := mq.Limit(2).All(ctx)
 	if err != nil {
@@ -146,7 +150,9 @@ func (mq *MetaQuery) OnlyX(ctx context.Context) *Meta {
 	return node
 }
 
-// OnlyID returns the only Meta id in the query, returns an error if not exactly one id was returned.
+// OnlyID is like Only, but returns the only Meta ID in the query.
+// Returns a *NotSingularError when exactly one Meta ID is not found.
+// Returns a *NotFoundError when no entities are found.
 func (mq *MetaQuery) OnlyID(ctx context.Context) (id int, err error) {
 	var ids []int
 	if ids, err = mq.Limit(2).IDs(ctx); err != nil {
@@ -189,7 +195,7 @@ func (mq *MetaQuery) AllX(ctx context.Context) []*Meta {
 	return nodes
 }
 
-// IDs executes the query and returns a list of Meta ids.
+// IDs executes the query and returns a list of Meta IDs.
 func (mq *MetaQuery) IDs(ctx context.Context) ([]int, error) {
 	var ids []int
 	if err := mq.Select(meta.FieldID).Scan(ctx, &ids); err != nil {
@@ -241,24 +247,27 @@ func (mq *MetaQuery) ExistX(ctx context.Context) bool {
 	return exist
 }
 
-// Clone returns a duplicate of the query builder, including all associated steps. It can be
+// Clone returns a duplicate of the MetaQuery builder, including all associated steps. It can be
 // used to prepare common query builders and use them differently after the clone is made.
 func (mq *MetaQuery) Clone() *MetaQuery {
+	if mq == nil {
+		return nil
+	}
 	return &MetaQuery{
 		config:     mq.config,
 		limit:      mq.limit,
 		offset:     mq.offset,
 		order:      append([]OrderFunc{}, mq.order...),
-		unique:     append([]string{}, mq.unique...),
 		predicates: append([]predicate.Meta{}, mq.predicates...),
+		withOwner:  mq.withOwner.Clone(),
 		// clone intermediate query.
 		sql:  mq.sql.Clone(),
 		path: mq.path,
 	}
 }
 
-//  WithOwner tells the query-builder to eager-loads the nodes that are connected to
-// the "owner" edge. The optional arguments used to configure the query builder of the edge.
+// WithOwner tells the query-builder to eager-load the nodes that are connected to
+// the "owner" edge. The optional arguments are used to configure the query builder of the edge.
 func (mq *MetaQuery) WithOwner(opts ...func(*AlertQuery)) *MetaQuery {
 	query := &AlertQuery{config: mq.config}
 	for _, opt := range opts {
@@ -268,7 +277,7 @@ func (mq *MetaQuery) WithOwner(opts ...func(*AlertQuery)) *MetaQuery {
 	return mq
 }
 
-// GroupBy used to group vertices by one or more fields/columns.
+// GroupBy is used to group vertices by one or more fields/columns.
 // It is often used with aggregate functions, like: count, max, mean, min, sum.
 //
 // Example:
@@ -295,7 +304,8 @@ func (mq *MetaQuery) GroupBy(field string, fields ...string) *MetaGroupBy {
 	return group
 }
 
-// Select one or more fields from the given query.
+// Select allows the selection one or more fields/columns for the given query,
+// instead of selecting all fields in the entity.
 //
 // Example:
 //
@@ -308,18 +318,16 @@ func (mq *MetaQuery) GroupBy(field string, fields ...string) *MetaGroupBy {
 //		Scan(ctx, &v)
 //
 func (mq *MetaQuery) Select(field string, fields ...string) *MetaSelect {
-	selector := &MetaSelect{config: mq.config}
-	selector.fields = append([]string{field}, fields...)
-	selector.path = func(ctx context.Context) (prev *sql.Selector, err error) {
-		if err := mq.prepareQuery(ctx); err != nil {
-			return nil, err
-		}
-		return mq.sqlQuery(), nil
-	}
-	return selector
+	mq.fields = append([]string{field}, fields...)
+	return &MetaSelect{MetaQuery: mq}
 }
 
 func (mq *MetaQuery) prepareQuery(ctx context.Context) error {
+	for _, f := range mq.fields {
+		if !meta.ValidColumn(f) {
+			return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
+		}
+	}
 	if mq.path != nil {
 		prev, err := mq.path(ctx)
 		if err != nil {
@@ -345,22 +353,18 @@ func (mq *MetaQuery) sqlAll(ctx context.Context) ([]*Meta, error) {
 	if withFKs {
 		_spec.Node.Columns = append(_spec.Node.Columns, meta.ForeignKeys...)
 	}
-	_spec.ScanValues = func() []interface{} {
+	_spec.ScanValues = func(columns []string) ([]interface{}, error) {
 		node := &Meta{config: mq.config}
 		nodes = append(nodes, node)
-		values := node.scanValues()
-		if withFKs {
-			values = append(values, node.fkValues()...)
-		}
-		return values
+		return node.scanValues(columns)
 	}
-	_spec.Assign = func(values ...interface{}) error {
+	_spec.Assign = func(columns []string, values []interface{}) error {
 		if len(nodes) == 0 {
 			return fmt.Errorf("ent: Assign called without calling ScanValues")
 		}
 		node := nodes[len(nodes)-1]
 		node.Edges.loadedTypes = loadedTypes
-		return node.assignValues(values...)
+		return node.assignValues(columns, values)
 	}
 	if err := sqlgraph.QueryNodes(ctx, mq.driver, _spec); err != nil {
 		return nil, err
@@ -423,6 +427,15 @@ func (mq *MetaQuery) querySpec() *sqlgraph.QuerySpec {
 		From:   mq.sql,
 		Unique: true,
 	}
+	if fields := mq.fields; len(fields) > 0 {
+		_spec.Node.Columns = make([]string, 0, len(fields))
+		_spec.Node.Columns = append(_spec.Node.Columns, meta.FieldID)
+		for i := range fields {
+			if fields[i] != meta.FieldID {
+				_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
+			}
+		}
+	}
 	if ps := mq.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
@@ -471,7 +484,7 @@ func (mq *MetaQuery) sqlQuery() *sql.Selector {
 	return selector
 }
 
-// MetaGroupBy is the builder for group-by Meta entities.
+// MetaGroupBy is the group-by builder for Meta entities.
 type MetaGroupBy struct {
 	config
 	fields []string
@@ -487,7 +500,7 @@ func (mgb *MetaGroupBy) Aggregate(fns ...AggregateFunc) *MetaGroupBy {
 	return mgb
 }
 
-// Scan applies the group-by query and scan the result into the given value.
+// Scan applies the group-by query and scans the result into the given value.
 func (mgb *MetaGroupBy) Scan(ctx context.Context, v interface{}) error {
 	query, err := mgb.path(ctx)
 	if err != nil {
@@ -504,7 +517,8 @@ func (mgb *MetaGroupBy) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from group-by. It is only allowed when querying group-by with one field.
+// Strings returns list of strings from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MetaGroupBy) Strings(ctx context.Context) ([]string, error) {
 	if len(mgb.fields) > 1 {
 		return nil, errors.New("ent: MetaGroupBy.Strings is not achievable when grouping more than 1 field")
@@ -525,7 +539,8 @@ func (mgb *MetaGroupBy) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from group-by. It is only allowed when querying group-by with one field.
+// String returns a single string from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MetaGroupBy) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = mgb.Strings(ctx); err != nil {
@@ -551,7 +566,8 @@ func (mgb *MetaGroupBy) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from group-by. It is only allowed when querying group-by with one field.
+// Ints returns list of ints from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MetaGroupBy) Ints(ctx context.Context) ([]int, error) {
 	if len(mgb.fields) > 1 {
 		return nil, errors.New("ent: MetaGroupBy.Ints is not achievable when grouping more than 1 field")
@@ -572,7 +588,8 @@ func (mgb *MetaGroupBy) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from group-by. It is only allowed when querying group-by with one field.
+// Int returns a single int from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MetaGroupBy) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = mgb.Ints(ctx); err != nil {
@@ -598,7 +615,8 @@ func (mgb *MetaGroupBy) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from group-by. It is only allowed when querying group-by with one field.
+// Float64s returns list of float64s from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MetaGroupBy) Float64s(ctx context.Context) ([]float64, error) {
 	if len(mgb.fields) > 1 {
 		return nil, errors.New("ent: MetaGroupBy.Float64s is not achievable when grouping more than 1 field")
@@ -619,7 +637,8 @@ func (mgb *MetaGroupBy) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from group-by. It is only allowed when querying group-by with one field.
+// Float64 returns a single float64 from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MetaGroupBy) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = mgb.Float64s(ctx); err != nil {
@@ -645,7 +664,8 @@ func (mgb *MetaGroupBy) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from group-by. It is only allowed when querying group-by with one field.
+// Bools returns list of bools from group-by.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MetaGroupBy) Bools(ctx context.Context) ([]bool, error) {
 	if len(mgb.fields) > 1 {
 		return nil, errors.New("ent: MetaGroupBy.Bools is not achievable when grouping more than 1 field")
@@ -666,7 +686,8 @@ func (mgb *MetaGroupBy) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from group-by. It is only allowed when querying group-by with one field.
+// Bool returns a single bool from a group-by query.
+// It is only allowed when executing a group-by query with one field.
 func (mgb *MetaGroupBy) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = mgb.Bools(ctx); err != nil {
@@ -721,22 +742,19 @@ func (mgb *MetaGroupBy) sqlQuery() *sql.Selector {
 	return selector.Select(columns...).GroupBy(mgb.fields...)
 }
 
-// MetaSelect is the builder for select fields of Meta entities.
+// MetaSelect is the builder for selecting fields of Meta entities.
 type MetaSelect struct {
-	config
-	fields []string
+	*MetaQuery
 	// intermediate query (i.e. traversal path).
-	sql  *sql.Selector
-	path func(context.Context) (*sql.Selector, error)
+	sql *sql.Selector
 }
 
-// Scan applies the selector query and scan the result into the given value.
+// Scan applies the selector query and scans the result into the given value.
 func (ms *MetaSelect) Scan(ctx context.Context, v interface{}) error {
-	query, err := ms.path(ctx)
-	if err != nil {
+	if err := ms.prepareQuery(ctx); err != nil {
 		return err
 	}
-	ms.sql = query
+	ms.sql = ms.MetaQuery.sqlQuery()
 	return ms.sqlScan(ctx, v)
 }
 
@@ -747,7 +765,7 @@ func (ms *MetaSelect) ScanX(ctx context.Context, v interface{}) {
 	}
 }
 
-// Strings returns list of strings from selector. It is only allowed when selecting one field.
+// Strings returns list of strings from a selector. It is only allowed when selecting one field.
 func (ms *MetaSelect) Strings(ctx context.Context) ([]string, error) {
 	if len(ms.fields) > 1 {
 		return nil, errors.New("ent: MetaSelect.Strings is not achievable when selecting more than 1 field")
@@ -768,7 +786,7 @@ func (ms *MetaSelect) StringsX(ctx context.Context) []string {
 	return v
 }
 
-// String returns a single string from selector. It is only allowed when selecting one field.
+// String returns a single string from a selector. It is only allowed when selecting one field.
 func (ms *MetaSelect) String(ctx context.Context) (_ string, err error) {
 	var v []string
 	if v, err = ms.Strings(ctx); err != nil {
@@ -794,7 +812,7 @@ func (ms *MetaSelect) StringX(ctx context.Context) string {
 	return v
 }
 
-// Ints returns list of ints from selector. It is only allowed when selecting one field.
+// Ints returns list of ints from a selector. It is only allowed when selecting one field.
 func (ms *MetaSelect) Ints(ctx context.Context) ([]int, error) {
 	if len(ms.fields) > 1 {
 		return nil, errors.New("ent: MetaSelect.Ints is not achievable when selecting more than 1 field")
@@ -815,7 +833,7 @@ func (ms *MetaSelect) IntsX(ctx context.Context) []int {
 	return v
 }
 
-// Int returns a single int from selector. It is only allowed when selecting one field.
+// Int returns a single int from a selector. It is only allowed when selecting one field.
 func (ms *MetaSelect) Int(ctx context.Context) (_ int, err error) {
 	var v []int
 	if v, err = ms.Ints(ctx); err != nil {
@@ -841,7 +859,7 @@ func (ms *MetaSelect) IntX(ctx context.Context) int {
 	return v
 }
 
-// Float64s returns list of float64s from selector. It is only allowed when selecting one field.
+// Float64s returns list of float64s from a selector. It is only allowed when selecting one field.
 func (ms *MetaSelect) Float64s(ctx context.Context) ([]float64, error) {
 	if len(ms.fields) > 1 {
 		return nil, errors.New("ent: MetaSelect.Float64s is not achievable when selecting more than 1 field")
@@ -862,7 +880,7 @@ func (ms *MetaSelect) Float64sX(ctx context.Context) []float64 {
 	return v
 }
 
-// Float64 returns a single float64 from selector. It is only allowed when selecting one field.
+// Float64 returns a single float64 from a selector. It is only allowed when selecting one field.
 func (ms *MetaSelect) Float64(ctx context.Context) (_ float64, err error) {
 	var v []float64
 	if v, err = ms.Float64s(ctx); err != nil {
@@ -888,7 +906,7 @@ func (ms *MetaSelect) Float64X(ctx context.Context) float64 {
 	return v
 }
 
-// Bools returns list of bools from selector. It is only allowed when selecting one field.
+// Bools returns list of bools from a selector. It is only allowed when selecting one field.
 func (ms *MetaSelect) Bools(ctx context.Context) ([]bool, error) {
 	if len(ms.fields) > 1 {
 		return nil, errors.New("ent: MetaSelect.Bools is not achievable when selecting more than 1 field")
@@ -909,7 +927,7 @@ func (ms *MetaSelect) BoolsX(ctx context.Context) []bool {
 	return v
 }
 
-// Bool returns a single bool from selector. It is only allowed when selecting one field.
+// Bool returns a single bool from a selector. It is only allowed when selecting one field.
 func (ms *MetaSelect) Bool(ctx context.Context) (_ bool, err error) {
 	var v []bool
 	if v, err = ms.Bools(ctx); err != nil {
@@ -936,11 +954,6 @@ func (ms *MetaSelect) BoolX(ctx context.Context) bool {
 }
 
 func (ms *MetaSelect) sqlScan(ctx context.Context, v interface{}) error {
-	for _, f := range ms.fields {
-		if !meta.ValidColumn(f) {
-			return &ValidationError{Name: f, err: fmt.Errorf("invalid field %q for selection", f)}
-		}
-	}
 	rows := &sql.Rows{}
 	query, args := ms.sqlQuery().Query()
 	if err := ms.driver.Query(ctx, query, args, rows); err != nil {

+ 28 - 29
pkg/database/ent/meta_update.go

@@ -18,24 +18,23 @@ import (
 // MetaUpdate is the builder for updating Meta entities.
 type MetaUpdate struct {
 	config
-	hooks      []Hook
-	mutation   *MetaMutation
-	predicates []predicate.Meta
+	hooks    []Hook
+	mutation *MetaMutation
 }
 
-// Where adds a new predicate for the builder.
+// Where adds a new predicate for the MetaUpdate builder.
 func (mu *MetaUpdate) Where(ps ...predicate.Meta) *MetaUpdate {
-	mu.predicates = append(mu.predicates, ps...)
+	mu.mutation.predicates = append(mu.mutation.predicates, ps...)
 	return mu
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (mu *MetaUpdate) SetCreatedAt(t time.Time) *MetaUpdate {
 	mu.mutation.SetCreatedAt(t)
 	return mu
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (mu *MetaUpdate) SetNillableCreatedAt(t *time.Time) *MetaUpdate {
 	if t != nil {
 		mu.SetCreatedAt(*t)
@@ -43,13 +42,13 @@ func (mu *MetaUpdate) SetNillableCreatedAt(t *time.Time) *MetaUpdate {
 	return mu
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (mu *MetaUpdate) SetUpdatedAt(t time.Time) *MetaUpdate {
 	mu.mutation.SetUpdatedAt(t)
 	return mu
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (mu *MetaUpdate) SetNillableUpdatedAt(t *time.Time) *MetaUpdate {
 	if t != nil {
 		mu.SetUpdatedAt(*t)
@@ -57,25 +56,25 @@ func (mu *MetaUpdate) SetNillableUpdatedAt(t *time.Time) *MetaUpdate {
 	return mu
 }
 
-// SetKey sets the key field.
+// SetKey sets the "key" field.
 func (mu *MetaUpdate) SetKey(s string) *MetaUpdate {
 	mu.mutation.SetKey(s)
 	return mu
 }
 
-// SetValue sets the value field.
+// SetValue sets the "value" field.
 func (mu *MetaUpdate) SetValue(s string) *MetaUpdate {
 	mu.mutation.SetValue(s)
 	return mu
 }
 
-// SetOwnerID sets the owner edge to Alert by id.
+// SetOwnerID sets the "owner" edge to the Alert entity by ID.
 func (mu *MetaUpdate) SetOwnerID(id int) *MetaUpdate {
 	mu.mutation.SetOwnerID(id)
 	return mu
 }
 
-// SetNillableOwnerID sets the owner edge to Alert by id if the given value is not nil.
+// SetNillableOwnerID sets the "owner" edge to the Alert entity by ID if the given value is not nil.
 func (mu *MetaUpdate) SetNillableOwnerID(id *int) *MetaUpdate {
 	if id != nil {
 		mu = mu.SetOwnerID(*id)
@@ -83,7 +82,7 @@ func (mu *MetaUpdate) SetNillableOwnerID(id *int) *MetaUpdate {
 	return mu
 }
 
-// SetOwner sets the owner edge to Alert.
+// SetOwner sets the "owner" edge to the Alert entity.
 func (mu *MetaUpdate) SetOwner(a *Alert) *MetaUpdate {
 	return mu.SetOwnerID(a.ID)
 }
@@ -93,13 +92,13 @@ func (mu *MetaUpdate) Mutation() *MetaMutation {
 	return mu.mutation
 }
 
-// ClearOwner clears the "owner" edge to type Alert.
+// ClearOwner clears the "owner" edge to the Alert entity.
 func (mu *MetaUpdate) ClearOwner() *MetaUpdate {
 	mu.mutation.ClearOwner()
 	return mu
 }
 
-// Save executes the query and returns the number of rows/vertices matched by this operation.
+// Save executes the query and returns the number of nodes affected by the update operation.
 func (mu *MetaUpdate) Save(ctx context.Context) (int, error) {
 	var (
 		err      error
@@ -177,7 +176,7 @@ func (mu *MetaUpdate) sqlSave(ctx context.Context) (n int, err error) {
 			},
 		},
 	}
-	if ps := mu.predicates; len(ps) > 0 {
+	if ps := mu.mutation.predicates; len(ps) > 0 {
 		_spec.Predicate = func(selector *sql.Selector) {
 			for i := range ps {
 				ps[i](selector)
@@ -265,13 +264,13 @@ type MetaUpdateOne struct {
 	mutation *MetaMutation
 }
 
-// SetCreatedAt sets the created_at field.
+// SetCreatedAt sets the "created_at" field.
 func (muo *MetaUpdateOne) SetCreatedAt(t time.Time) *MetaUpdateOne {
 	muo.mutation.SetCreatedAt(t)
 	return muo
 }
 
-// SetNillableCreatedAt sets the created_at field if the given value is not nil.
+// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
 func (muo *MetaUpdateOne) SetNillableCreatedAt(t *time.Time) *MetaUpdateOne {
 	if t != nil {
 		muo.SetCreatedAt(*t)
@@ -279,13 +278,13 @@ func (muo *MetaUpdateOne) SetNillableCreatedAt(t *time.Time) *MetaUpdateOne {
 	return muo
 }
 
-// SetUpdatedAt sets the updated_at field.
+// SetUpdatedAt sets the "updated_at" field.
 func (muo *MetaUpdateOne) SetUpdatedAt(t time.Time) *MetaUpdateOne {
 	muo.mutation.SetUpdatedAt(t)
 	return muo
 }
 
-// SetNillableUpdatedAt sets the updated_at field if the given value is not nil.
+// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
 func (muo *MetaUpdateOne) SetNillableUpdatedAt(t *time.Time) *MetaUpdateOne {
 	if t != nil {
 		muo.SetUpdatedAt(*t)
@@ -293,25 +292,25 @@ func (muo *MetaUpdateOne) SetNillableUpdatedAt(t *time.Time) *MetaUpdateOne {
 	return muo
 }
 
-// SetKey sets the key field.
+// SetKey sets the "key" field.
 func (muo *MetaUpdateOne) SetKey(s string) *MetaUpdateOne {
 	muo.mutation.SetKey(s)
 	return muo
 }
 
-// SetValue sets the value field.
+// SetValue sets the "value" field.
 func (muo *MetaUpdateOne) SetValue(s string) *MetaUpdateOne {
 	muo.mutation.SetValue(s)
 	return muo
 }
 
-// SetOwnerID sets the owner edge to Alert by id.
+// SetOwnerID sets the "owner" edge to the Alert entity by ID.
 func (muo *MetaUpdateOne) SetOwnerID(id int) *MetaUpdateOne {
 	muo.mutation.SetOwnerID(id)
 	return muo
 }
 
-// SetNillableOwnerID sets the owner edge to Alert by id if the given value is not nil.
+// SetNillableOwnerID sets the "owner" edge to the Alert entity by ID if the given value is not nil.
 func (muo *MetaUpdateOne) SetNillableOwnerID(id *int) *MetaUpdateOne {
 	if id != nil {
 		muo = muo.SetOwnerID(*id)
@@ -319,7 +318,7 @@ func (muo *MetaUpdateOne) SetNillableOwnerID(id *int) *MetaUpdateOne {
 	return muo
 }
 
-// SetOwner sets the owner edge to Alert.
+// SetOwner sets the "owner" edge to the Alert entity.
 func (muo *MetaUpdateOne) SetOwner(a *Alert) *MetaUpdateOne {
 	return muo.SetOwnerID(a.ID)
 }
@@ -329,13 +328,13 @@ func (muo *MetaUpdateOne) Mutation() *MetaMutation {
 	return muo.mutation
 }
 
-// ClearOwner clears the "owner" edge to type Alert.
+// ClearOwner clears the "owner" edge to the Alert entity.
 func (muo *MetaUpdateOne) ClearOwner() *MetaUpdateOne {
 	muo.mutation.ClearOwner()
 	return muo
 }
 
-// Save executes the query and returns the updated entity.
+// Save executes the query and returns the updated Meta entity.
 func (muo *MetaUpdateOne) Save(ctx context.Context) (*Meta, error) {
 	var (
 		err  error
@@ -483,7 +482,7 @@ func (muo *MetaUpdateOne) sqlSave(ctx context.Context) (_node *Meta, err error)
 	}
 	_node = &Meta{config: muo.config}
 	_spec.Assign = _node.assignValues
-	_spec.ScanValues = _node.scanValues()
+	_spec.ScanValues = _node.scanValues
 	if err = sqlgraph.UpdateNode(ctx, muo.driver, _spec); err != nil {
 		if _, ok := err.(*sqlgraph.NotFoundError); ok {
 			err = &NotFoundError{meta.Label}

+ 2 - 0
pkg/database/ent/migrate/migrate.go

@@ -31,6 +31,8 @@ var (
 	// WithFixture sets the foreign-key renaming option to the migration when upgrading
 	// ent from v0.1.0 (issue-#285). Defaults to false.
 	WithFixture = schema.WithFixture
+	// WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true.
+	WithForeignKeys = schema.WithForeignKeys
 )
 
 // Schema is the API for creating, migrating and dropping a schema.

+ 4 - 1
pkg/database/ent/migrate/schema.go

@@ -81,6 +81,9 @@ var (
 		{Name: "type", Type: field.TypeString},
 		{Name: "start_ip", Type: field.TypeInt64, Nullable: true},
 		{Name: "end_ip", Type: field.TypeInt64, Nullable: true},
+		{Name: "start_suffix", Type: field.TypeInt64, Nullable: true},
+		{Name: "end_suffix", Type: field.TypeInt64, Nullable: true},
+		{Name: "ip_size", Type: field.TypeInt64, Nullable: true},
 		{Name: "scope", Type: field.TypeString},
 		{Name: "value", Type: field.TypeString},
 		{Name: "origin", Type: field.TypeString},
@@ -95,7 +98,7 @@ var (
 		ForeignKeys: []*schema.ForeignKey{
 			{
 				Symbol:  "decisions_alerts_decisions",
-				Columns: []*schema.Column{DecisionsColumns[12]},
+				Columns: []*schema.Column{DecisionsColumns[15]},
 
 				RefColumns: []*schema.Column{AlertsColumns[0]},
 				OnDelete:   schema.SetNull,

File diff suppressed because it is too large
+ 163 - 179
pkg/database/ent/mutation.go


+ 0 - 355
pkg/database/ent/privacy/privacy.go

@@ -1,355 +0,0 @@
-// Code generated by entc, DO NOT EDIT.
-
-package privacy
-
-import (
-	"context"
-	"errors"
-	"fmt"
-
-	"github.com/crowdsecurity/crowdsec/pkg/database/ent"
-)
-
-var (
-	// Allow may be returned by rules to indicate that the policy
-	// evaluation should terminate with an allow decision.
-	Allow = errors.New("ent/privacy: allow rule")
-
-	// Deny may be returned by rules to indicate that the policy
-	// evaluation should terminate with an deny decision.
-	Deny = errors.New("ent/privacy: deny rule")
-
-	// Skip may be returned by rules to indicate that the policy
-	// evaluation should continue to the next rule.
-	Skip = errors.New("ent/privacy: skip rule")
-)
-
-// Allowf returns an formatted wrapped Allow decision.
-func Allowf(format string, a ...interface{}) error {
-	return fmt.Errorf(format+": %w", append(a, Allow)...)
-}
-
-// Denyf returns an formatted wrapped Deny decision.
-func Denyf(format string, a ...interface{}) error {
-	return fmt.Errorf(format+": %w", append(a, Deny)...)
-}
-
-// Skipf returns an formatted wrapped Skip decision.
-func Skipf(format string, a ...interface{}) error {
-	return fmt.Errorf(format+": %w", append(a, Skip)...)
-}
-
-type decisionCtxKey struct{}
-
-// DecisionContext creates a decision context.
-func DecisionContext(parent context.Context, decision error) context.Context {
-	if decision == nil || errors.Is(decision, Skip) {
-		return parent
-	}
-	return context.WithValue(parent, decisionCtxKey{}, decision)
-}
-
-func decisionFromContext(ctx context.Context) (error, bool) {
-	decision, ok := ctx.Value(decisionCtxKey{}).(error)
-	if ok && errors.Is(decision, Allow) {
-		decision = nil
-	}
-	return decision, ok
-}
-
-type (
-	// QueryPolicy combines multiple query rules into a single policy.
-	QueryPolicy []QueryRule
-
-	// QueryRule defines the interface deciding whether a
-	// query is allowed and optionally modify it.
-	QueryRule interface {
-		EvalQuery(context.Context, ent.Query) error
-	}
-)
-
-// EvalQuery evaluates a query against a query policy.
-func (policy QueryPolicy) EvalQuery(ctx context.Context, q ent.Query) error {
-	if decision, ok := decisionFromContext(ctx); ok {
-		return decision
-	}
-	for _, rule := range policy {
-		switch decision := rule.EvalQuery(ctx, q); {
-		case decision == nil || errors.Is(decision, Skip):
-		case errors.Is(decision, Allow):
-			return nil
-		default:
-			return decision
-		}
-	}
-	return nil
-}
-
-// QueryRuleFunc type is an adapter to allow the use of
-// ordinary functions as query rules.
-type QueryRuleFunc func(context.Context, ent.Query) error
-
-// Eval returns f(ctx, q).
-func (f QueryRuleFunc) EvalQuery(ctx context.Context, q ent.Query) error {
-	return f(ctx, q)
-}
-
-type (
-	// MutationPolicy combines multiple mutation rules into a single policy.
-	MutationPolicy []MutationRule
-
-	// MutationRule defines the interface deciding whether a
-	// mutation is allowed and optionally modify it.
-	MutationRule interface {
-		EvalMutation(context.Context, ent.Mutation) error
-	}
-)
-
-// EvalMutation evaluates a mutation against a mutation policy.
-func (policy MutationPolicy) EvalMutation(ctx context.Context, m ent.Mutation) error {
-	if decision, ok := decisionFromContext(ctx); ok {
-		return decision
-	}
-	for _, rule := range policy {
-		switch decision := rule.EvalMutation(ctx, m); {
-		case decision == nil || errors.Is(decision, Skip):
-		case errors.Is(decision, Allow):
-			return nil
-		default:
-			return decision
-		}
-	}
-	return nil
-}
-
-// MutationRuleFunc type is an adapter to allow the use of
-// ordinary functions as mutation rules.
-type MutationRuleFunc func(context.Context, ent.Mutation) error
-
-// EvalMutation returns f(ctx, m).
-func (f MutationRuleFunc) EvalMutation(ctx context.Context, m ent.Mutation) error {
-	return f(ctx, m)
-}
-
-// Policy groups query and mutation policies.
-type Policy struct {
-	Query    QueryPolicy
-	Mutation MutationPolicy
-}
-
-// EvalQuery forwards evaluation to query policy.
-func (policy Policy) EvalQuery(ctx context.Context, q ent.Query) error {
-	return policy.Query.EvalQuery(ctx, q)
-}
-
-// EvalMutation forwards evaluation to mutation policy.
-func (policy Policy) EvalMutation(ctx context.Context, m ent.Mutation) error {
-	return policy.Mutation.EvalMutation(ctx, m)
-}
-
-// QueryMutationRule is the interface that groups query and mutation rules.
-type QueryMutationRule interface {
-	QueryRule
-	MutationRule
-}
-
-// AlwaysAllowRule returns a rule that returns an allow decision.
-func AlwaysAllowRule() QueryMutationRule {
-	return fixedDecision{Allow}
-}
-
-// AlwaysDenyRule returns a rule that returns a deny decision.
-func AlwaysDenyRule() QueryMutationRule {
-	return fixedDecision{Deny}
-}
-
-type fixedDecision struct {
-	decision error
-}
-
-func (f fixedDecision) EvalQuery(context.Context, ent.Query) error {
-	return f.decision
-}
-
-func (f fixedDecision) EvalMutation(context.Context, ent.Mutation) error {
-	return f.decision
-}
-
-type contextDecision struct {
-	eval func(context.Context) error
-}
-
-// ContextQueryMutationRule creates a query/mutation rule from a context eval func.
-func ContextQueryMutationRule(eval func(context.Context) error) QueryMutationRule {
-	return contextDecision{eval}
-}
-
-func (c contextDecision) EvalQuery(ctx context.Context, _ ent.Query) error {
-	return c.eval(ctx)
-}
-
-func (c contextDecision) EvalMutation(ctx context.Context, _ ent.Mutation) error {
-	return c.eval(ctx)
-}
-
-// OnMutationOperation evaluates the given rule only on a given mutation operation.
-func OnMutationOperation(rule MutationRule, op ent.Op) MutationRule {
-	return MutationRuleFunc(func(ctx context.Context, m ent.Mutation) error {
-		if m.Op().Is(op) {
-			return rule.EvalMutation(ctx, m)
-		}
-		return Skip
-	})
-}
-
-// DenyMutationOperationRule returns a rule denying specified mutation operation.
-func DenyMutationOperationRule(op ent.Op) MutationRule {
-	rule := MutationRuleFunc(func(_ context.Context, m ent.Mutation) error {
-		return Denyf("ent/privacy: operation %s is not allowed", m.Op())
-	})
-	return OnMutationOperation(rule, op)
-}
-
-// The AlertQueryRuleFunc type is an adapter to allow the use of ordinary
-// functions as a query rule.
-type AlertQueryRuleFunc func(context.Context, *ent.AlertQuery) error
-
-// EvalQuery return f(ctx, q).
-func (f AlertQueryRuleFunc) EvalQuery(ctx context.Context, q ent.Query) error {
-	if q, ok := q.(*ent.AlertQuery); ok {
-		return f(ctx, q)
-	}
-	return Denyf("ent/privacy: unexpected query type %T, expect *ent.AlertQuery", q)
-}
-
-// The AlertMutationRuleFunc type is an adapter to allow the use of ordinary
-// functions as a mutation rule.
-type AlertMutationRuleFunc func(context.Context, *ent.AlertMutation) error
-
-// EvalMutation calls f(ctx, m).
-func (f AlertMutationRuleFunc) EvalMutation(ctx context.Context, m ent.Mutation) error {
-	if m, ok := m.(*ent.AlertMutation); ok {
-		return f(ctx, m)
-	}
-	return Denyf("ent/privacy: unexpected mutation type %T, expect *ent.AlertMutation", m)
-}
-
-// The BouncerQueryRuleFunc type is an adapter to allow the use of ordinary
-// functions as a query rule.
-type BouncerQueryRuleFunc func(context.Context, *ent.BouncerQuery) error
-
-// EvalQuery return f(ctx, q).
-func (f BouncerQueryRuleFunc) EvalQuery(ctx context.Context, q ent.Query) error {
-	if q, ok := q.(*ent.BouncerQuery); ok {
-		return f(ctx, q)
-	}
-	return Denyf("ent/privacy: unexpected query type %T, expect *ent.BouncerQuery", q)
-}
-
-// The BouncerMutationRuleFunc type is an adapter to allow the use of ordinary
-// functions as a mutation rule.
-type BouncerMutationRuleFunc func(context.Context, *ent.BouncerMutation) error
-
-// EvalMutation calls f(ctx, m).
-func (f BouncerMutationRuleFunc) EvalMutation(ctx context.Context, m ent.Mutation) error {
-	if m, ok := m.(*ent.BouncerMutation); ok {
-		return f(ctx, m)
-	}
-	return Denyf("ent/privacy: unexpected mutation type %T, expect *ent.BouncerMutation", m)
-}
-
-// The DecisionQueryRuleFunc type is an adapter to allow the use of ordinary
-// functions as a query rule.
-type DecisionQueryRuleFunc func(context.Context, *ent.DecisionQuery) error
-
-// EvalQuery return f(ctx, q).
-func (f DecisionQueryRuleFunc) EvalQuery(ctx context.Context, q ent.Query) error {
-	if q, ok := q.(*ent.DecisionQuery); ok {
-		return f(ctx, q)
-	}
-	return Denyf("ent/privacy: unexpected query type %T, expect *ent.DecisionQuery", q)
-}
-
-// The DecisionMutationRuleFunc type is an adapter to allow the use of ordinary
-// functions as a mutation rule.
-type DecisionMutationRuleFunc func(context.Context, *ent.DecisionMutation) error
-
-// EvalMutation calls f(ctx, m).
-func (f DecisionMutationRuleFunc) EvalMutation(ctx context.Context, m ent.Mutation) error {
-	if m, ok := m.(*ent.DecisionMutation); ok {
-		return f(ctx, m)
-	}
-	return Denyf("ent/privacy: unexpected mutation type %T, expect *ent.DecisionMutation", m)
-}
-
-// The EventQueryRuleFunc type is an adapter to allow the use of ordinary
-// functions as a query rule.
-type EventQueryRuleFunc func(context.Context, *ent.EventQuery) error
-
-// EvalQuery return f(ctx, q).
-func (f EventQueryRuleFunc) EvalQuery(ctx context.Context, q ent.Query) error {
-	if q, ok := q.(*ent.EventQuery); ok {
-		return f(ctx, q)
-	}
-	return Denyf("ent/privacy: unexpected query type %T, expect *ent.EventQuery", q)
-}
-
-// The EventMutationRuleFunc type is an adapter to allow the use of ordinary
-// functions as a mutation rule.
-type EventMutationRuleFunc func(context.Context, *ent.EventMutation) error
-
-// EvalMutation calls f(ctx, m).
-func (f EventMutationRuleFunc) EvalMutation(ctx context.Context, m ent.Mutation) error {
-	if m, ok := m.(*ent.EventMutation); ok {
-		return f(ctx, m)
-	}
-	return Denyf("ent/privacy: unexpected mutation type %T, expect *ent.EventMutation", m)
-}
-
-// The MachineQueryRuleFunc type is an adapter to allow the use of ordinary
-// functions as a query rule.
-type MachineQueryRuleFunc func(context.Context, *ent.MachineQuery) error
-
-// EvalQuery return f(ctx, q).
-func (f MachineQueryRuleFunc) EvalQuery(ctx context.Context, q ent.Query) error {
-	if q, ok := q.(*ent.MachineQuery); ok {
-		return f(ctx, q)
-	}
-	return Denyf("ent/privacy: unexpected query type %T, expect *ent.MachineQuery", q)
-}
-
-// The MachineMutationRuleFunc type is an adapter to allow the use of ordinary
-// functions as a mutation rule.
-type MachineMutationRuleFunc func(context.Context, *ent.MachineMutation) error
-
-// EvalMutation calls f(ctx, m).
-func (f MachineMutationRuleFunc) EvalMutation(ctx context.Context, m ent.Mutation) error {
-	if m, ok := m.(*ent.MachineMutation); ok {
-		return f(ctx, m)
-	}
-	return Denyf("ent/privacy: unexpected mutation type %T, expect *ent.MachineMutation", m)
-}
-
-// The MetaQueryRuleFunc type is an adapter to allow the use of ordinary
-// functions as a query rule.
-type MetaQueryRuleFunc func(context.Context, *ent.MetaQuery) error
-
-// EvalQuery return f(ctx, q).
-func (f MetaQueryRuleFunc) EvalQuery(ctx context.Context, q ent.Query) error {
-	if q, ok := q.(*ent.MetaQuery); ok {
-		return f(ctx, q)
-	}
-	return Denyf("ent/privacy: unexpected query type %T, expect *ent.MetaQuery", q)
-}
-
-// The MetaMutationRuleFunc type is an adapter to allow the use of ordinary
-// functions as a mutation rule.
-type MetaMutationRuleFunc func(context.Context, *ent.MetaMutation) error
-
-// EvalMutation calls f(ctx, m).
-func (f MetaMutationRuleFunc) EvalMutation(ctx context.Context, m ent.Mutation) error {
-	if m, ok := m.(*ent.MetaMutation); ok {
-		return f(ctx, m)
-	}
-	return Denyf("ent/privacy: unexpected mutation type %T, expect *ent.MetaMutation", m)
-}

+ 3 - 3
pkg/database/ent/runtime.go

@@ -14,8 +14,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/database/ent/schema"
 )
 
-// The init function reads all schema descriptors with runtime
-// code (default values, validators or hooks) and stitches it
+// The init function reads all schema descriptors with runtime code
+// (default values, validators, hooks and policies) and stitches it
 // to their package variables.
 func init() {
 	alertFields := schema.Alert{}.Fields()
@@ -85,7 +85,7 @@ func init() {
 	// decision.DefaultUpdatedAt holds the default value on creation for the updated_at field.
 	decision.DefaultUpdatedAt = decisionDescUpdatedAt.Default.(func() time.Time)
 	// decisionDescSimulated is the schema descriptor for simulated field.
-	decisionDescSimulated := decisionFields[10].Descriptor()
+	decisionDescSimulated := decisionFields[13].Descriptor()
 	// decision.DefaultSimulated holds the default value on creation for the simulated field.
 	decision.DefaultSimulated = decisionDescSimulated.Default.(bool)
 	eventFields := schema.Event{}.Fields()

+ 2 - 2
pkg/database/ent/runtime/runtime.go

@@ -5,6 +5,6 @@ package runtime
 // The schema-stitching logic is generated in github.com/crowdsecurity/crowdsec/pkg/database/ent/runtime.go
 
 const (
-	Version = "v0.4.3"                                          // Version of ent codegen.
-	Sum     = "h1:ds9HENceKzpGBgCRlkZNq6TqBIegwKcF3e5reuV9Z0M=" // Sum of ent codegen.
+	Version = "v0.5.4"                                          // Version of ent codegen.
+	Sum     = "h1:kIf2BQUdRJ7XrlTXzCyJCg69ar1K1FjFR2UQWRo/M8M=" // Sum of ent codegen.
 )

+ 3 - 0
pkg/database/ent/schema/decision.go

@@ -25,6 +25,9 @@ func (Decision) Fields() []ent.Field {
 		field.String("type"),
 		field.Int64("start_ip").Optional(),
 		field.Int64("end_ip").Optional(),
+		field.Int64("start_suffix").Optional(),
+		field.Int64("end_suffix").Optional(),
+		field.Int64("ip_size").Optional(),
 		field.String("scope"),
 		field.String("value"),
 		field.String("origin"),

+ 0 - 6
pkg/models/decision.go

@@ -21,9 +21,6 @@ type Decision struct {
 	// Required: true
 	Duration *string `json:"duration"`
 
-	// (only relevant for GET ops) when the value is an IP or range, its numeric representation
-	EndIP int64 `json:"end_ip,omitempty"`
-
 	// (only relevant for GET ops) the unique id
 	// Read Only: true
 	ID int64 `json:"id,omitempty"`
@@ -44,9 +41,6 @@ type Decision struct {
 	// Read Only: true
 	Simulated *bool `json:"simulated,omitempty"`
 
-	// (only relevant for GET ops) when the value is an IP or range, its numeric representation
-	StartIP int64 `json:"start_ip,omitempty"`
-
 	// the type of decision, might be 'ban', 'captcha' or something custom. Ignored when watcher (cscli/crowdsec) is pushing to APIL.
 	// Required: true
 	Type *string `json:"type"`

+ 0 - 6
pkg/models/localapi_swagger.yaml

@@ -802,12 +802,6 @@ definitions:
       value:
         description: 'the value of the decision scope : an IP, a range, a username, etc'
         type: string
-      start_ip:
-        description: '(only relevant for GET ops) when the value is an IP or range, its numeric representation'
-        type: integer
-      end_ip:
-        description: '(only relevant for GET ops) when the value is an IP or range, its numeric representation'
-        type: integer
       duration:
         type: string
       scenario:

+ 106 - 0
pkg/types/ip.go

@@ -0,0 +1,106 @@
+package types
+
+import (
+	"encoding/binary"
+	"fmt"
+	"math"
+	"net"
+	"strings"
+
+	"github.com/pkg/errors"
+)
+
+func LastAddress(n net.IPNet) net.IP {
+	ip := n.IP.To4()
+	if ip == nil {
+		ip = n.IP
+		return net.IP{
+			ip[0] | ^n.Mask[0], ip[1] | ^n.Mask[1], ip[2] | ^n.Mask[2],
+			ip[3] | ^n.Mask[3], ip[4] | ^n.Mask[4], ip[5] | ^n.Mask[5],
+			ip[6] | ^n.Mask[6], ip[7] | ^n.Mask[7], ip[8] | ^n.Mask[8],
+			ip[9] | ^n.Mask[9], ip[10] | ^n.Mask[10], ip[11] | ^n.Mask[11],
+			ip[12] | ^n.Mask[12], ip[13] | ^n.Mask[13], ip[14] | ^n.Mask[14],
+			ip[15] | ^n.Mask[15]}
+	}
+
+	return net.IPv4(
+		ip[0]|^n.Mask[0],
+		ip[1]|^n.Mask[1],
+		ip[2]|^n.Mask[2],
+		ip[3]|^n.Mask[3])
+}
+
+/*returns a range for any ip or range*/
+func Addr2Ints(any string) (int, int64, int64, int64, int64, error) {
+	if strings.Contains(any, "/") {
+		_, net, err := net.ParseCIDR(any)
+		if err != nil {
+			return -1, 0, 0, 0, 0, errors.Wrapf(err, "while parsing range %s", any)
+		}
+		return Range2Ints(*net)
+	} else {
+		ip := net.ParseIP(any)
+		if ip == nil {
+			return -1, 0, 0, 0, 0, fmt.Errorf("invalid address")
+		}
+		sz, start, end, err := IP2Ints(ip)
+		if err != nil {
+			return -1, 0, 0, 0, 0, errors.Wrapf(err, "while parsing ip %s", any)
+		}
+		return sz, start, end, start, end, nil
+	}
+}
+
+/*size (16|4), nw_start, suffix_start, nw_end, suffix_end, error*/
+func Range2Ints(network net.IPNet) (int, int64, int64, int64, int64, error) {
+
+	szStart, nwStart, sfxStart, err := IP2Ints(network.IP)
+	if err != nil {
+		return -1, 0, 0, 0, 0, errors.Wrap(err, "converting first ip in range")
+	}
+	lastAddr := LastAddress(network)
+	szEnd, nwEnd, sfxEnd, err := IP2Ints(lastAddr)
+	if err != nil {
+		return -1, 0, 0, 0, 0, errors.Wrap(err, "transforming last address of range")
+	}
+	if szEnd != szStart {
+		return -1, 0, 0, 0, 0, fmt.Errorf("inconsistent size for range first(%d) and last(%d) ip", szStart, szEnd)
+	}
+	return szStart, nwStart, sfxStart, nwEnd, sfxEnd, nil
+}
+
+func uint2int(u uint64) int64 {
+	var ret int64
+	if u == math.MaxInt64 {
+		ret = 0
+	} else if u == math.MaxUint64 {
+		ret = math.MaxInt64
+	} else if u > math.MaxInt64 {
+		u -= math.MaxInt64
+		ret = int64(u)
+	} else {
+		ret = int64(u)
+		ret -= math.MaxInt64
+	}
+	return ret
+}
+
+/*size (16|4), network, suffix, error*/
+func IP2Ints(pip net.IP) (int, int64, int64, error) {
+	var ip_nw, ip_sfx uint64
+
+	pip4 := pip.To4()
+	pip16 := pip.To16()
+
+	if pip4 != nil {
+		ip_nw32 := binary.BigEndian.Uint32(pip4)
+
+		return 4, uint2int(uint64(ip_nw32)), uint2int(ip_sfx), nil
+	} else if pip16 != nil {
+		ip_nw = binary.BigEndian.Uint64(pip16[0:8])
+		ip_sfx = binary.BigEndian.Uint64(pip16[8:16])
+		return 16, uint2int(ip_nw), uint2int(ip_sfx), nil
+	} else {
+		return -1, 0, 0, fmt.Errorf("unexpected len %d for %s", len(pip), pip)
+	}
+}

+ 220 - 0
pkg/types/ip_test.go

@@ -0,0 +1,220 @@
+package types
+
+import (
+	"math"
+	"net"
+	"strings"
+	"testing"
+)
+
+func TestIP2Int(t *testing.T) {
+
+	tEmpty := net.IP{}
+	_, _, _, err := IP2Ints(tEmpty)
+	if !strings.Contains(err.Error(), "unexpected len 0 for <nil>") {
+		t.Fatalf("unexpected: %s", err.Error())
+	}
+}
+func TestRange2Int(t *testing.T) {
+	tEmpty := net.IPNet{}
+	//empty item
+	_, _, _, _, _, err := Range2Ints(tEmpty)
+	if !strings.Contains(err.Error(), "converting first ip in range") {
+		t.Fatalf("unexpected: %s", err.Error())
+	}
+
+}
+
+func TestAdd2Int(t *testing.T) {
+	tests := []struct {
+		in_addr       string
+		exp_sz        int
+		exp_start_ip  int64
+		exp_start_sfx int64
+		exp_end_ip    int64
+		exp_end_sfx   int64
+		exp_error     string
+	}{
+		{
+			in_addr: "7FFF:FFFF:FFFF:FFFF:aaaa:aaaa:aaaa:fff7",
+
+			exp_sz:        16,
+			exp_start_ip:  -math.MaxInt64 + 0x7FFFFFFFFFFFFFFF,
+			exp_start_sfx: -math.MaxInt64 + 0xaaaaaaaaaaaafff7,
+			exp_end_ip:    -math.MaxInt64 + 0x7FFFFFFFFFFFFFFF,
+			exp_end_sfx:   -math.MaxInt64 + 0xaaaaaaaaaaaafff7,
+		},
+		{
+			in_addr: "aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:fff7",
+
+			exp_sz:        16,
+			exp_start_ip:  -math.MaxInt64 + 0xaaaaaaaaaaaaaaaa,
+			exp_start_sfx: -math.MaxInt64 + 0xaaaaaaaaaaaafff7,
+			exp_end_ip:    -math.MaxInt64 + 0xaaaaaaaaaaaaaaaa,
+			exp_end_sfx:   -math.MaxInt64 + 0xaaaaaaaaaaaafff7,
+		},
+		{
+			in_addr: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff7",
+			/*ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff*/
+
+			exp_sz:        16,
+			exp_start_ip:  math.MaxInt64,
+			exp_start_sfx: -math.MaxInt64 + 0xfffffffffffffff7,
+			exp_end_ip:    math.MaxInt64,
+			exp_end_sfx:   -math.MaxInt64 + 0xfffffffffffffff7,
+		},
+		{
+			in_addr: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+			/*ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff*/
+
+			exp_sz:        16,
+			exp_start_ip:  math.MaxInt64,
+			exp_start_sfx: math.MaxInt64,
+			exp_end_ip:    math.MaxInt64,
+			exp_end_sfx:   math.MaxInt64,
+		},
+		{
+			in_addr: "::",
+			/*::*/
+
+			exp_sz:        16,
+			exp_start_ip:  -math.MaxInt64,
+			exp_start_sfx: -math.MaxInt64,
+			exp_end_ip:    -math.MaxInt64,
+			exp_end_sfx:   -math.MaxInt64,
+		},
+		{
+			in_addr: "2001:db8::",
+			/*2001:db8:: -> 2001:db8::*/
+			exp_sz:        16,
+			exp_start_ip:  -math.MaxInt64 + 0x20010DB800000000,
+			exp_start_sfx: -math.MaxInt64,
+			exp_end_ip:    -math.MaxInt64 + 0x20010DB800000000,
+			exp_end_sfx:   -math.MaxInt64,
+		},
+		{
+			in_addr: "2001:db8:0000:0000:0000:0000:0000:00ff",
+			/*2001:db8:0000:0000:0000:0000:0000:00ff*/
+			exp_sz:        16,
+			exp_start_ip:  -math.MaxInt64 + 0x20010DB800000000,
+			exp_start_sfx: -math.MaxInt64 + 0xFF,
+			exp_end_ip:    -math.MaxInt64 + 0x20010DB800000000,
+			exp_end_sfx:   -math.MaxInt64 + 0xFF,
+		},
+		{
+			in_addr: "1.2.3.4",
+			/*1.2.3.4*/
+			exp_sz:        4,
+			exp_start_ip:  -math.MaxInt64 + 0x01020304,
+			exp_start_sfx: 0,
+			exp_end_ip:    -math.MaxInt64 + 0x01020304,
+			exp_end_sfx:   0,
+		},
+		{
+			in_addr: "::/0",
+			/*:: -> ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff*/
+
+			exp_sz:        16,
+			exp_start_ip:  -math.MaxInt64,
+			exp_start_sfx: -math.MaxInt64,
+			exp_end_ip:    math.MaxInt64,
+			exp_end_sfx:   math.MaxInt64,
+		},
+		{
+			in_addr: "::/64",
+			/*:: -> 0000:0000:0000:0000:ffff:ffff:ffff:ffff*/
+			exp_sz:        16,
+			exp_start_ip:  -math.MaxInt64,
+			exp_start_sfx: -math.MaxInt64,
+			exp_end_ip:    -math.MaxInt64,
+			exp_end_sfx:   math.MaxInt64,
+		},
+		{
+			in_addr: "2001:db8::/109",
+			/*2001:db8:: -> 2001:db8:0000:0000:0000:0000:0007:ffff*/
+			exp_sz:        16,
+			exp_start_ip:  -math.MaxInt64 + 0x20010DB800000000,
+			exp_start_sfx: -math.MaxInt64,
+			exp_end_ip:    -math.MaxInt64 + 0x20010DB800000000,
+			exp_end_sfx:   -math.MaxInt64 + 0x7FFFF,
+		},
+		{
+			in_addr: "0.0.0.0/0",
+			/*0.0.0.0 -> 255.255.255.255*/
+			exp_sz:        4,
+			exp_start_ip:  -math.MaxInt64,
+			exp_start_sfx: 0,
+			exp_end_ip:    -math.MaxInt64 + 0xFFFFFFFF,
+			exp_end_sfx:   0,
+		},
+		{
+			in_addr: "0.0.0.0/16",
+			/*0.0.0.0 -> 0.0.255.255*/
+			exp_sz:        4,
+			exp_start_ip:  -math.MaxInt64,
+			exp_start_sfx: 0,
+			exp_end_ip:    -math.MaxInt64 + 0x0000FFFF,
+			exp_end_sfx:   0,
+		},
+		{
+			in_addr: "255.255.0.0/16",
+			/*255.255.0.0 -> 255.255.255.255*/
+			exp_sz:        4,
+			exp_start_ip:  -math.MaxInt64 + 0xFFFF0000,
+			exp_start_sfx: 0,
+			exp_end_ip:    -math.MaxInt64 + 0xFFFFFFFF,
+			exp_end_sfx:   0,
+		},
+		{
+			in_addr: "1.2.3.0/24",
+			/*1.2.3.0 -> 1.2.3.255*/
+			exp_sz:        4,
+			exp_start_ip:  -math.MaxInt64 + 0x01020300,
+			exp_start_sfx: 0,
+			exp_end_ip:    -math.MaxInt64 + 0x010203FF,
+			exp_end_sfx:   0,
+		},
+		/*errors*/
+		{
+			in_addr:   "xxx/24",
+			exp_error: "invalid CIDR address",
+		},
+		{
+			in_addr:   "xxx2",
+			exp_error: "invalid address",
+		},
+	}
+
+	for idx, test := range tests {
+		sz, start_ip, start_sfx, end_ip, end_sfx, err := Addr2Ints(test.in_addr)
+		if err != nil && test.exp_error == "" {
+			t.Fatalf("%d unexpected error : %s", idx, err)
+		}
+		if test.exp_error != "" {
+			if !strings.Contains(err.Error(), test.exp_error) {
+				t.Fatalf("%d unmatched error : %s != %s", idx, err, test.exp_error)
+			}
+			continue //we can skip this one
+		}
+		if sz != test.exp_sz {
+			t.Fatalf("%d unexpected size %d != %d", idx, sz, test.exp_sz)
+		}
+		if start_ip != test.exp_start_ip {
+			t.Fatalf("%d unexpected start_ip %d != %d", idx, start_ip, test.exp_start_ip)
+		}
+		if sz == 16 {
+			if start_sfx != test.exp_start_sfx {
+				t.Fatalf("%d unexpected start sfx %d != %d", idx, start_sfx, test.exp_start_sfx)
+			}
+		}
+		if end_ip != test.exp_end_ip {
+			t.Fatalf("%d unexpected end ip %d != %d", idx, end_ip, test.exp_end_ip)
+		}
+		if sz == 16 {
+			if end_sfx != test.exp_end_sfx {
+				t.Fatalf("%d unexpected end sfx %d != %d", idx, end_sfx, test.exp_end_sfx)
+			}
+		}
+
+	}
+}

+ 0 - 37
pkg/types/utils.go

@@ -2,7 +2,6 @@ package types
 
 import (
 	"bytes"
-	"encoding/binary"
 	"encoding/gob"
 	"fmt"
 	"io"
@@ -21,42 +20,6 @@ import (
 	"gopkg.in/natefinch/lumberjack.v2"
 )
 
-func IP2Int(ip net.IP) uint32 {
-	if len(ip) == 16 {
-		return binary.BigEndian.Uint32(ip[12:16])
-	}
-	return binary.BigEndian.Uint32(ip)
-}
-
-func Int2ip(nn uint32) net.IP {
-	ip := make(net.IP, 4)
-	binary.BigEndian.PutUint32(ip, nn)
-	return ip
-}
-
-//Stolen from : https://github.com/llimllib/ipaddress/
-// Return the final address of a net range. Convert to IPv4 if possible,
-// otherwise return an ipv6
-func LastAddress(n *net.IPNet) net.IP {
-	ip := n.IP.To4()
-	if ip == nil {
-		ip = n.IP
-		return net.IP{
-			ip[0] | ^n.Mask[0], ip[1] | ^n.Mask[1], ip[2] | ^n.Mask[2],
-			ip[3] | ^n.Mask[3], ip[4] | ^n.Mask[4], ip[5] | ^n.Mask[5],
-			ip[6] | ^n.Mask[6], ip[7] | ^n.Mask[7], ip[8] | ^n.Mask[8],
-			ip[9] | ^n.Mask[9], ip[10] | ^n.Mask[10], ip[11] | ^n.Mask[11],
-			ip[12] | ^n.Mask[12], ip[13] | ^n.Mask[13], ip[14] | ^n.Mask[14],
-			ip[15] | ^n.Mask[15]}
-	}
-
-	return net.IPv4(
-		ip[0]|^n.Mask[0],
-		ip[1]|^n.Mask[1],
-		ip[2]|^n.Mask[2],
-		ip[3]|^n.Mask[3])
-}
-
 var logFormatter log.Formatter
 var LogOutput *lumberjack.Logger //io.Writer
 var logLevel log.Level

+ 434 - 0
scripts/test_ip_management.sh

@@ -0,0 +1,434 @@
+#! /usr/bin/env bash
+# -*- coding: utf-8 -*-
+
+
+# Codes
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+NC='\033[0m'
+OK_STR="${GREEN}OK${NC}"
+FAIL_STR="${RED}FAIL${NC}"
+
+CSCLI_BIN="./cscli"
+CSCLI="${CSCLI_BIN} -c dev.yaml"
+JQ="jq -e"
+CROWDSEC_API_URL="http://localhost:8081"
+CROWDSEC_VERSION=""
+API_KEY=""
+
+RELEASE_FOLDER_FULL=""
+FAILED="false"
+MUST_FAIL="false"
+
+
+get_latest_release() {
+  CROWDSEC_VERSION=$(curl --silent "https://api.github.com/repos/crowdsecurity/crowdsec/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+}
+
+### Helpers
+function docurl
+{
+    URI=$1
+    curl -s -H "X-Api-Key: ${API_KEY}" "${CROWDSEC_API_URL}${URI}"
+} 
+
+function bouncer_echo {
+    if [[ ${FAILED} == "false" ]];
+    then
+        echo -e "[bouncer] $1: ${OK_STR}"
+    else
+        echo -e "[bouncer] $1: ${FAIL_STR}"
+    fi
+    FAILED="false"
+}
+
+function cscli_echo {
+    if [[ ${FAILED} == "false" ]];
+    then
+        echo -e "[cscli]   $1: ${OK_STR}"
+    else
+        echo -e "[cscli]   $1: ${FAIL_STR}"
+    fi
+    FAILED="false"
+}
+
+function fail {
+    FAILED="true"
+    MUST_FAIL="true"
+}
+
+## End helpers ##
+
+
+function init
+{
+    if [[ ! -d ${RELEASE_FOLDER} ]];
+    then
+      cd ..
+      get_latest_release
+      BUILD_VERSION=${CROWDSEC_VERSION} make release
+      if [ $? != 0 ]; then
+        echo "Unable to make the release (make sur you have go installed), exiting"
+        exit 1
+      fi
+      RELEASE_FOLDER="crowdsec-${CROWDSEC_VERSION}"
+    fi
+    RELEASE_FOLDER_FULL="$(readlink -f ${RELEASE_FOLDER})"
+    TEST_ENV_FOLDER="${RELEASE_FOLDER_FULL}/tests/"
+    cd ${RELEASE_FOLDER}/
+    if [[ ! -d "${TEST_ENV_FOLDER}" ]];
+    then
+        echo "Installing crowdsec test environment"
+        ./test_env.sh
+    fi
+    reset
+}
+
+
+function test_ipv4_ip
+{
+    echo ""
+    echo "##########################################"
+    echo "$FUNCNAME"
+    echo "##########################################"
+    echo ""
+    
+    ${CSCLI} decisions list -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "first decisions list"
+    
+    docurl /v1/decisions | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "first bouncer decisions request (must be empty)"
+
+    #add ip decision
+    echo "adding decision for 1.2.3.4"
+    ${CSCLI} decisions add -i 1.2.3.4  > /dev/null 2>&1 || fail
+    
+    ${CSCLI} decisions list -o json | ${JQ} '.[].decisions[0].value == "1.2.3.4"' > /dev/null || fail
+    cscli_echo "getting all decision"
+    
+    docurl /v1/decisions | ${JQ} '.[0].value == "1.2.3.4"' > /dev/null || fail
+    bouncer_echo "getting all decision"
+
+    #check ip match
+    ${CSCLI} decisions list -i 1.2.3.4 -o json | ${JQ} '.[].decisions[0].value == "1.2.3.4"'  > /dev/null || fail
+    cscli_echo "getting decision for 1.2.3.4"
+    
+    docurl /v1/decisions?ip=1.2.3.4 | ${JQ} '.[0].value == "1.2.3.4"' > /dev/null || fail
+    bouncer_echo "getting decision for 1.2.3.4"
+
+    ${CSCLI} decisions list -i 1.2.3.5 -o json | ${JQ} '. == null'  > /dev/null || fail
+    cscli_echo "getting decision for 1.2.3.5"
+
+    docurl /v1/decisions?ip=1.2.3.5 | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decision for 1.2.3.5"
+
+    #check outer range match
+    ${CSCLI} decisions list -r 1.2.3.0/24 -o json | ${JQ} '. == null'  > /dev/null || fail
+    cscli_echo "getting decision for 1.2.3.0/24"
+
+    docurl "/v1/decisions?range=1.2.3.0/24" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decision for 1.2.3.0/24"
+
+    ${CSCLI} decisions list -r 1.2.3.0/24 --contained -o json |${JQ} '.[].decisions[0].value == "1.2.3.4"'  > /dev/null || fail
+    cscli_echo "getting decisions where IP in 1.2.3.0/24"
+
+    docurl "/v1/decisions?range=1.2.3.0/24&contains=false" | ${JQ} '.[0].value == "1.2.3.4"' > /dev/null || fail
+    bouncer_echo "getting decisions where IP in 1.2.3.0/24"
+
+}
+
+function test_ipv4_range
+{
+    echo ""
+    echo "##########################################"
+    echo "$FUNCNAME"
+    echo "##########################################"
+    echo ""
+
+    cscli_echo "adding decision for range 4.4.4.0/24"
+    ${CSCLI} decisions add -r 4.4.4.0/24 > /dev/null 2>&1 || fail
+
+    ${CSCLI} decisions list -o json | ${JQ} '.[0].decisions[0].value == "4.4.4.0/24", .[1].decisions[0].value == "1.2.3.4"'> /dev/null || fail
+    cscli_echo "getting all decision"
+    
+    docurl ${APIK} "/v1/decisions" | ${JQ} '.[0].value == "1.2.3.4", .[1].value == "4.4.4.0/24"'> /dev/null || fail
+    bouncer_echo "getting all decision"
+
+    #check ip within/outside of range
+    ${CSCLI} decisions list -i 4.4.4.3 -o json | ${JQ} '.[].decisions[0].value == "4.4.4.0/24"' > /dev/null || fail
+    cscli_echo "getting decisions for ip 4.4.4."
+
+    docurl ${APIK} "/v1/decisions?ip=4.4.4.3" | ${JQ} '.[0].value == "4.4.4.0/24"' > /dev/null || fail
+    bouncer_echo "getting decisions for ip 4.4.4."
+
+    ${CSCLI} decisions list -i 4.4.4.4 -o json --contained | ${JQ} '. == null'> /dev/null || fail
+    cscli_echo "getting decisions for ip contained in 4.4.4."
+
+    docurl ${APIK} "/v1/decisions?ip=4.4.4.4&contains=false" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for ip contained in 4.4.4."
+
+    ${CSCLI} decisions list -i 5.4.4.3 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for ip 5.4.4."
+
+    docurl ${APIK} "/v1/decisions?ip=5.4.4.3" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for ip 5.4.4."
+
+    ${CSCLI} decisions list -r 4.4.0.0/16 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for range 4.4.0.0/1"
+
+    docurl ${APIK} "/v1/decisions?range=4.4.0.0/16" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for range 4.4.0.0/1"
+
+    ${CSCLI} decisions list -r 4.4.0.0/16 -o json --contained | ${JQ} '.[].decisions[0].value == "4.4.4.0/24"' > /dev/null || fail
+    cscli_echo "getting decisions for ip/range in 4.4.0.0/1"
+
+    docurl ${APIK} "/v1/decisions?range=4.4.0.0/16&contains=false" | ${JQ} '.[0].value == "4.4.4.0/24"' > /dev/null || fail
+    bouncer_echo "getting decisions for ip/range in 4.4.0.0/1"
+
+    #check subrange
+    ${CSCLI} decisions list -r 4.4.4.2/28 -o json | ${JQ} '.[].decisions[0].value == "4.4.4.0/24"' > /dev/null || fail
+    cscli_echo "getting decisions for range 4.4.4.2/2"
+
+    docurl ${APIK} "/v1/decisions?range=4.4.4.2/28" | ${JQ} '.[].value == "4.4.4.0/24"' > /dev/null || fail
+    bouncer_echo "getting decisions for range 4.4.4.2/2"
+
+    ${CSCLI} decisions list -r 4.4.3.2/28 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for range 4.4.3.2/2"
+
+    docurl ${APIK} "/v1/decisions?range=4.4.3.2/28" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for range 4.4.3.2/2"
+
+}
+
+function test_ipv6_ip
+{
+
+    echo ""
+    echo "##########################################"
+    echo "$FUNCNAME"
+    echo "##########################################"
+    echo ""
+
+    cscli_echo "adding decision for ip 1111:2222:3333:4444:5555:6666:7777:8888"
+    ${CSCLI} decisions add -i 1111:2222:3333:4444:5555:6666:7777:8888 > /dev/null 2>&1
+
+    ${CSCLI} decisions list -o json | ${JQ} '.[].decisions[0].value == "1111:2222:3333:4444:5555:6666:7777:8888"'  > /dev/null || fail
+    cscli_echo "getting all decision"
+
+    docurl ${APIK} "/v1/decisions" | ${JQ} '.[].value == "1111:2222:3333:4444:5555:6666:7777:8888"' > /dev/null || fail
+    bouncer_echo "getting all decision"
+
+    ${CSCLI} decisions list -i 1111:2222:3333:4444:5555:6666:7777:8888 -o json | ${JQ} '.[].decisions[0].value == "1111:2222:3333:4444:5555:6666:7777:8888"'  > /dev/null || fail
+    cscli_echo "getting decisions for ip 1111:2222:3333:4444:5555:6666:7777:8888"
+    
+    docurl ${APIK} "/v1/decisions?ip=1111:2222:3333:4444:5555:6666:7777:8888" | ${JQ} '.[].value == "1111:2222:3333:4444:5555:6666:7777:8888"' > /dev/null || fail
+    bouncer_echo "getting decisions for ip 1111:2222:3333:4444:5555:6666:7777:888"
+
+    ${CSCLI} decisions list -i 1211:2222:3333:4444:5555:6666:7777:8888 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for ip 1211:2222:3333:4444:5555:6666:7777:8888"
+
+    docurl ${APIK} "/v1/decisions?ip=1211:2222:3333:4444:5555:6666:7777:8888" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for ip 1211:2222:3333:4444:5555:6666:7777:888"
+
+    ${CSCLI} decisions list -i 1111:2222:3333:4444:5555:6666:7777:8887 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for ip 1111:2222:3333:4444:5555:6666:7777:8887"
+
+    docurl ${APIK} "/v1/decisions?ip=1111:2222:3333:4444:5555:6666:7777:8887" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for ip 1111:2222:3333:4444:5555:6666:7777:888"
+
+    ${CSCLI} decisions list -r 1111:2222:3333:4444:5555:6666:7777:8888/48 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for range 1111:2222:3333:4444:5555:6666:7777:8888/48"
+
+    docurl ${APIK} "/v1/decisions?range=1111:2222:3333:4444:5555:6666:7777:8888/48" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for range 1111:2222:3333:4444:5555:6666:7777:8888/48"
+
+    ${CSCLI} decisions list -r 1111:2222:3333:4444:5555:6666:7777:8888/48 --contained -o json | ${JQ} '.[].decisions[0].value == "1111:2222:3333:4444:5555:6666:7777:8888"' > /dev/null || fail
+    cscli_echo "getting decisions for ip/range in range 1111:2222:3333:4444:5555:6666:7777:8888/48"
+
+    docurl ${APIK} "/v1/decisions?range=1111:2222:3333:4444:5555:6666:7777:8888/48&&contains=false" | ${JQ} '.[].value == "1111:2222:3333:4444:5555:6666:7777:8888"' > /dev/null || fail
+    bouncer_echo "getting decisions for ip/range in 1111:2222:3333:4444:5555:6666:7777:8888/48"
+
+    ${CSCLI} decisions list -r 1111:2222:3333:4444:5555:6666:7777:8888/64 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for range 1111:2222:3333:4444:5555:6666:7777:8888/64"
+
+    docurl ${APIK} "/v1/decisions?range=1111:2222:3333:4444:5555:6666:7777:8888/64" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for range 1111:2222:3333:4444:5555:6666:7777:8888/64"
+
+    ${CSCLI} decisions list -r 1111:2222:3333:4444:5555:6666:7777:8888/64 -o json --contained | ${JQ} '.[].decisions[0].value == "1111:2222:3333:4444:5555:6666:7777:8888"'  > /dev/null || fail
+    cscli_echo "getting decisions for ip/range in 1111:2222:3333:4444:5555:6666:7777:8888/64"
+
+    docurl ${APIK} "/v1/decisions?range=1111:2222:3333:4444:5555:6666:7777:8888/64&&contains=false" | ${JQ} '.[].value == "1111:2222:3333:4444:5555:6666:7777:8888"' > /dev/null || fail
+    bouncer_echo "getting decisions for ip/range in 1111:2222:3333:4444:5555:6666:7777:8888/64"
+
+}
+
+function test_ipv6_range
+{
+    echo ""
+    echo "##########################################"
+    echo "$FUNCNAME"
+    echo "##########################################"
+    echo ""
+
+    cscli_echo "adding decision for range aaaa:2222:3333:4444::/64"
+    ${CSCLI} decisions add -r aaaa:2222:3333:4444::/64 > /dev/null 2>&1 || fail
+     
+    ${CSCLI} decisions list -o json | ${JQ} '.[0].decisions[0].value == "aaaa:2222:3333:4444::/64", .[1].decisions[0].value == "1111:2222:3333:4444:5555:6666:7777:8888"' > /dev/null || fail
+    cscli_echo "getting all decision"
+
+    docurl ${APIK} "/v1/decisions" | ${JQ} '.[0].value == "1111:2222:3333:4444:5555:6666:7777:8888", .[1].value == "aaaa:2222:3333:4444::/64"' > /dev/null || fail
+    bouncer_echo "getting all decision"
+
+    #check ip within/out of range
+    ${CSCLI} decisions list -i aaaa:2222:3333:4444:5555:6666:7777:8888 -o json | ${JQ} '.[].decisions[0].value == "aaaa:2222:3333:4444::/64"'  > /dev/null || fail
+    cscli_echo "getting decisions for ip aaaa:2222:3333:4444:5555:6666:7777:8888"
+
+    docurl ${APIK} "/v1/decisions?ip=aaaa:2222:3333:4444:5555:6666:7777:8888" | ${JQ} '.[].value == "aaaa:2222:3333:4444::/64"' > /dev/null || fail
+    bouncer_echo "getting decisions for ip aaaa:2222:3333:4444:5555:6666:7777:8888"
+
+    ${CSCLI} decisions list -i aaaa:2222:3333:4445:5555:6666:7777:8888 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for ip aaaa:2222:3333:4445:5555:6666:7777:8888"
+
+    docurl ${APIK} "/v1/decisions?ip=aaaa:2222:3333:4445:5555:6666:7777:8888" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for ip aaaa:2222:3333:4445:5555:6666:7777:8888"
+
+    ${CSCLI} decisions list -i aaa1:2222:3333:4444:5555:6666:7777:8887 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for ip aaa1:2222:3333:4444:5555:6666:7777:8887"
+
+    docurl ${APIK} "/v1/decisions?ip=aaa1:2222:3333:4444:5555:6666:7777:8887" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for ip aaa1:2222:3333:4444:5555:6666:7777:8887"
+
+    #check subrange within/out of range
+    ${CSCLI} decisions list -r aaaa:2222:3333:4444:5555::/80 -o json | ${JQ} '.[].decisions[0].value == "aaaa:2222:3333:4444::/64"'  > /dev/null || fail
+    cscli_echo "getting decisions for range aaaa:2222:3333:4444:5555::/80"
+    
+    docurl ${APIK} "/v1/decisions?range=aaaa:2222:3333:4444:5555::/80" | ${JQ} '.[].value == "aaaa:2222:3333:4444::/64"' > /dev/null || fail
+    bouncer_echo "getting decisions for range aaaa:2222:3333:4444:5555::/80"
+
+    ${CSCLI} decisions list -r aaaa:2222:3333:4441:5555::/80 -o json | ${JQ} '. == null'  > /dev/null || fail
+    cscli_echo "getting decisions for range aaaa:2222:3333:4441:5555::/80"
+    
+    docurl ${APIK} "/v1/decisions?range=aaaa:2222:3333:4441:5555::/80" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for range aaaa:2222:3333:4441:5555::/80"
+
+    ${CSCLI} decisions list -r aaa1:2222:3333:4444:5555::/80 -o json | ${JQ} '. == null'  > /dev/null || fail
+    cscli_echo "getting decisions for range aaa1:2222:3333:4444:5555::/80"
+
+    docurl ${APIK} "/v1/decisions?range=aaa1:2222:3333:4444:5555::/80" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for range aaa1:2222:3333:4444:5555::/80"
+
+    #check outer range
+    ${CSCLI} decisions list -r aaaa:2222:3333:4444:5555:6666:7777:8888/48 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for range aaaa:2222:3333:4444:5555:6666:7777:8888/48"
+
+    docurl ${APIK} "/v1/decisions?range=aaaa:2222:3333:4444:5555:6666:7777:8888/48" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for range aaaa:2222:3333:4444:5555:6666:7777:8888/48"
+
+    ${CSCLI} decisions list -r aaaa:2222:3333:4444:5555:6666:7777:8888/48 -o json --contained | ${JQ} '.[].decisions[0].value == "aaaa:2222:3333:4444::/64"' > /dev/null || fail
+    cscli_echo "getting decisions for ip/range in aaaa:2222:3333:4444:5555:6666:7777:8888/48"
+
+    docurl ${APIK} "/v1/decisions?range=aaaa:2222:3333:4444:5555:6666:7777:8888/48&contains=false" | ${JQ} '.[].value == "aaaa:2222:3333:4444::/64"' > /dev/null || fail
+    bouncer_echo "getting decisions for ip/range in aaaa:2222:3333:4444:5555:6666:7777:8888/48"
+
+    ${CSCLI} decisions list -r aaaa:2222:3333:4445:5555:6666:7777:8888/48 -o json | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for ip/range aaaa:2222:3333:4445:5555:6666:7777:8888/48"
+
+    docurl ${APIK} "/v1/decisions?range=aaaa:2222:3333:4445:5555:6666:7777:8888/48" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for ip/range in aaaa:2222:3333:4445:5555:6666:7777:8888/48"
+
+    #bbbb:db8:: -> bbbb:db8:0000:0000:0000:7fff:ffff:ffff
+    ${CSCLI} decisions add -r bbbb:db8::/81 > /dev/null 2>&1
+    cscli_echo "adding decision for range bbbb:db8::/81" > /dev/null || fail
+
+    ${CSCLI} decisions list -o json -i bbbb:db8:0000:0000:0000:6fff:ffff:ffff | ${JQ} '.[].decisions[0].value == "bbbb:db8::/81"'  > /dev/null || fail
+    cscli_echo "getting decisions for ip bbbb:db8:0000:0000:0000:6fff:ffff:ffff"
+    
+    docurl ${APIK} "/v1/decisions?ip=bbbb:db8:0000:0000:0000:6fff:ffff:ffff" | ${JQ} '.[].value == "bbbb:db8::/81"' > /dev/null || fail
+    bouncer_echo "getting decisions for ip in bbbb:db8:0000:0000:0000:6fff:ffff:ffff"
+
+    ${CSCLI} decisions list -o json -i bbbb:db8:0000:0000:0000:8fff:ffff:ffff | ${JQ} '. == null' > /dev/null || fail
+    cscli_echo "getting decisions for ip bbbb:db8:0000:0000:0000:8fff:ffff:ffff"
+
+    docurl ${APIK} "/v1/decisions?ip=bbbb:db8:0000:0000:0000:8fff:ffff:ffff" | ${JQ} '. == null' > /dev/null || fail
+    bouncer_echo "getting decisions for ip in bbbb:db8:0000:0000:0000:8fff:ffff:ffff"
+
+}
+
+
+function start_test
+{
+
+    ## ipv4 testing
+    test_ipv4_ip
+    test_ipv4_range
+
+    reset
+
+    ## ipv6 testing
+    test_ipv6_ip
+    test_ipv6_range
+}
+
+function reset 
+{
+    cd ${RELEASE_FOLDER_FULL}/tests/
+    rm data/crowdsec.db > /dev/null 2>&1 || echo ""
+    killall crowdsec > /dev/null 2>&1 || echo ""
+    ${CSCLI} hub update
+    ${CSCLI} machines add -a
+    API_KEY=`${CSCLI} bouncers add TestingBouncer -o=raw`
+    ./crowdsec -c dev.yaml> crowdsec-out.log 2>&1  &
+    sleep 2
+}
+
+function down
+{
+  cd ${RELEASE_FOLDER_FULL}/tests/
+  rm data/crowdsec.db
+  killall crowdsec
+  #rm -rf tests/
+}
+
+
+usage() {
+      echo "Usage:"
+      echo ""
+      echo "    ./ip_mgmt_tests.sh -h                                   Display this help message."
+      echo "    ./ip_mgmt_tests.sh                                      Run all the testsuite. Go must be available to make the release"
+      echo "    ./ip_mgmt_tests.sh --release <path_to_release_folder>   If go is not installed, please provide a path to the crowdsec-vX.Y.Z release folder"
+      echo ""
+      exit 0  
+}
+
+while [[ $# -gt 0 ]]
+do
+    key="${1}"
+    case ${key} in
+    --release|-r)
+        RELEASE_FOLDER="${2}"
+        shift #past argument
+        shift
+        ;;   
+    -h|--help)
+        usage
+        exit 0
+        ;;
+    *)    # unknown option
+        echo "Unknown argument ${key}."
+        usage
+        exit 1
+        ;;
+    esac
+done
+
+
+init
+start_test
+down
+
+if [[ ${MUST_FAIL} == "true" ]];
+then
+    echo ""
+    echo "One or more tests have failed !"
+    exit 1
+fi

+ 1 - 1
scripts/test_wizard_upgrade.sh

@@ -69,7 +69,7 @@ function init
     wget https://github.com/crowdsecurity/cs-firewall-bouncer/releases/download/${BOUNCER_VERSION}/cs-firewall-bouncer.tgz
     tar xzvf cs-firewall-bouncer.tgz
     cd cs-firewall-bouncer-${BOUNCER_VERSION}/
-    echo "iptables" | sudo ./install.sh
+    (echo "iptables" | sudo ./install.sh) || (echo "Unable to install cs-firewall-bouncer" && exit 1)
     cd ${CURRENT_FOLDER}
 
     echo "[*] Tainting parser /etc/crowdsec/parsers/s01-parse/sshd-logs.yaml"

Some files were not shown because too many files changed in this diff