From 7c4a181cdfe6e63f752a88bc1658df7c980b1c55 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 4 Nov 2025 23:11:32 +0000 Subject: [PATCH] Add generated Bob database integration --- README.md | 12 + bobgen.yaml | 10 + database.go | 144 +++++ dberrors/bob_errors.bob.go | 32 + dberrors/bob_main.bob_test.go | 9 + dberrors/goose_db_version.bob.go | 17 + dberrors/organization.bob.go | 17 + dberrors/user_.bob.go | 17 + dbinfo/bob_types.bob.go | 83 +++ dbinfo/goose_db_version.bob.go | 122 ++++ dbinfo/organization.bob.go | 102 +++ dbinfo/user_.bob.go | 197 ++++++ enums/enums.bob.go | 118 ++++ factory/bobfactory_context.bob.go | 39 ++ factory/bobfactory_main.bob.go | 142 +++++ factory/bobfactory_main.bob_test.go | 81 +++ factory/bobfactory_random.bob.go | 76 +++ factory/bobfactory_random.bob_test.go | 57 ++ factory/goose_db_version.bob.go | 380 +++++++++++ factory/organization.bob.go | 406 ++++++++++++ factory/user_.bob.go | 865 ++++++++++++++++++++++++++ go.mod | 17 + go.sum | 72 +++ main.go | 15 +- migrations/00001_initial.sql | 39 ++ models/bob_joins.bob.go | 76 +++ models/bob_loaders.bob.go | 67 ++ models/bob_types.bob_test.go | 30 + models/bob_where.bob.go | 33 + models/goose_db_version.bob.go | 424 +++++++++++++ models/organization.bob.go | 622 ++++++++++++++++++ models/user_.bob.go | 809 ++++++++++++++++++++++++ 32 files changed, 5128 insertions(+), 2 deletions(-) create mode 100644 bobgen.yaml create mode 100644 database.go create mode 100644 dberrors/bob_errors.bob.go create mode 100644 dberrors/bob_main.bob_test.go create mode 100644 dberrors/goose_db_version.bob.go create mode 100644 dberrors/organization.bob.go create mode 100644 dberrors/user_.bob.go create mode 100644 dbinfo/bob_types.bob.go create mode 100644 dbinfo/goose_db_version.bob.go create mode 100644 dbinfo/organization.bob.go create mode 100644 dbinfo/user_.bob.go create mode 100644 enums/enums.bob.go create mode 100644 factory/bobfactory_context.bob.go create mode 100644 factory/bobfactory_main.bob.go create mode 100644 factory/bobfactory_main.bob_test.go create mode 100644 factory/bobfactory_random.bob.go create mode 100644 factory/bobfactory_random.bob_test.go create mode 100644 factory/goose_db_version.bob.go create mode 100644 factory/organization.bob.go create mode 100644 factory/user_.bob.go create mode 100644 migrations/00001_initial.sql create mode 100644 models/bob_joins.bob.go create mode 100644 models/bob_loaders.bob.go create mode 100644 models/bob_types.bob_test.go create mode 100644 models/bob_where.bob.go create mode 100644 models/goose_db_version.bob.go create mode 100644 models/organization.bob.go create mode 100644 models/user_.bob.go diff --git a/README.md b/README.md index 3bea72bc..77c9701e 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,15 @@ nix develop go build . ``` +## Hacking + +### bob + +This uses the bob query framework. You can regenerate the models for bob with: + +``` +PSQL_DSN="postgresql://dbname?host=/var/run/postgresql&sslmode=disable" go run github.com/stephenafamo/bob/gen/bobgen-psql@latest" +PSQL_DSN="postgresql://?host=/var/run/postgresql&sslmode=disable&dbname=nidus-sync" go run github.com/stephenafamo/bob/gen/bobgen-psql@latest +``` + +This will generate a bunch of files. They're already committed, you only need this if you change the database schema in some way. diff --git a/bobgen.yaml b/bobgen.yaml new file mode 100644 index 00000000..514c9e12 --- /dev/null +++ b/bobgen.yaml @@ -0,0 +1,10 @@ +aliases: + user_: + up_plural: "Users" + up_singular: "User" + down_plural: "users" + down_singular: "user" +psql: + queries: + - ./sql +plugins_preset: "all" diff --git a/database.go b/database.go new file mode 100644 index 00000000..17fd4b65 --- /dev/null +++ b/database.go @@ -0,0 +1,144 @@ +package main + +import ( + "context" + "database/sql" + "embed" + "errors" + "fmt" + "io/fs" + "log" + "sync" + + //"github.com/georgysavva/scany/v2/pgxscan" + //"github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/stdlib" + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/pressly/goose/v3" + "github.com/stephenafamo/bob" +) + +//go:embed migrations/*.sql +var embedMigrations embed.FS + +type postgres struct { + BobDB bob.DB +} + +var ( + PGInstance *postgres + pgOnce sync.Once +) + +func doMigrations(connection_string string) error { + log.Println("Connecting to database at", connection_string) + db, err := sql.Open("pgx", connection_string) + if err != nil { + return fmt.Errorf("Failed to open database connection: %w", err) + } + defer db.Close() + row := db.QueryRowContext(context.Background(), "SELECT version()") + var val string + if err := row.Scan(&val); err != nil { + return fmt.Errorf("Failed to get database version query result: %w", err) + } + log.Printf("Connected to: %s", val) + + fsys, err := fs.Sub(embedMigrations, "migrations") + if err != nil { + return fmt.Errorf("Failed to get migrations embedded directory: %w", err) + } + provider, err := goose.NewProvider(goose.DialectPostgres, db, fsys) + if err != nil { + return fmt.Errorf("Failed to create goose provider: %w", err) + } + //goose.SetBaseFS(embedMigrations) + + current, target, err := provider.GetVersions(context.Background()) + if err != nil { + return fmt.Errorf("Faield to get goose versions: %w", err) + } + log.Printf("Current version %d, need to be at version %d", current, target) + results, err := provider.Up(context.Background()) + if err != nil { + return fmt.Errorf("Failed to run migrations: %w", err) + } + if len(results) > 0 { + for _, r := range results { + log.Printf("Migration %d %s", r.Source.Version, r.Direction) + } + } else { + log.Println("No migrations necessary.") + } + return nil +} + +func initializeDatabase(ctx context.Context, uri string) error { + needs, err := needsMigrations(uri) + if err != nil { + return fmt.Errorf("Failed to determine if migrations are needed: %v", err) + } + if needs == nil { + return errors.New("Can't read variable 'needs' - it's nil") + } + if *needs { + //return errors.New(fmt.Sprintf("Must migrate database before connecting: %t", *needs)) + log.Println("Handling database migrations") + err = doMigrations(uri) + if err != nil { + return fmt.Errorf("Failed to handle migrations: %v", err) + } + } else { + log.Println("No database migrations necessary") + } + + pgOnce.Do(func() { + db, e := pgxpool.New(ctx, uri) + bobDB := bob.NewDB(stdlib.OpenDBFromPool(db)) + PGInstance = &postgres{bobDB} + err = e + }) + if err != nil { + return fmt.Errorf("unable to create connection pool: %w", err) + } + + var current string + query := `SELECT current_database()` + err = PGInstance.BobDB.QueryRow(query).Scan(¤t) + if err != nil { + return fmt.Errorf("Failed to get database current: %w", err) + } + log.Println("Connected to", current) + return nil +} + +func needsMigrations(connection_string string) (*bool, error) { + log.Println("Connecting to database at", connection_string) + db, err := sql.Open("pgx", connection_string) + if err != nil { + return nil, fmt.Errorf("Failed to open database connection: %w", err) + } + defer db.Close() + row := db.QueryRowContext(context.Background(), "SELECT version()") + var val string + if err := row.Scan(&val); err != nil { + return nil, fmt.Errorf("Failed to get database version query result: %w", err) + } + log.Printf("Connected to: %s", val) + + fsys, err := fs.Sub(embedMigrations, "migrations") + if err != nil { + return nil, fmt.Errorf("Failed to get migrations embedded directory: %w", err) + } + provider, err := goose.NewProvider(goose.DialectPostgres, db, fsys) + if err != nil { + return nil, fmt.Errorf("Failed to create goose provider: %w", err) + } + + hasPending, err := provider.HasPending(context.Background()) + if err != nil { + return nil, err + } + return &hasPending, nil +} diff --git a/dberrors/bob_errors.bob.go b/dberrors/bob_errors.bob.go new file mode 100644 index 00000000..9195df5a --- /dev/null +++ b/dberrors/bob_errors.bob.go @@ -0,0 +1,32 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +import "github.com/lib/pq" + +// ErrUniqueConstraint captures all unique constraint errors by explicitly leaving `s` empty. +var ErrUniqueConstraint = &UniqueConstraintError{s: ""} + +type UniqueConstraintError struct { + // schema is the schema where the unique constraint is defined. + schema string + // table is the name of the table where the unique constraint is defined. + table string + // columns are the columns constituting the unique constraint. + columns []string + // s is a string uniquely identifying the constraint in the raw error message returned from the database. + s string +} + +func (e *UniqueConstraintError) Error() string { + return e.s +} + +func (e *UniqueConstraintError) Is(target error) bool { + err, ok := target.(*pq.Error) + if !ok { + return false + } + return err.Code == "23505" && (e.s == "" || err.Constraint == e.s) +} diff --git a/dberrors/bob_main.bob_test.go b/dberrors/bob_main.bob_test.go new file mode 100644 index 00000000..b9da2c5e --- /dev/null +++ b/dberrors/bob_main.bob_test.go @@ -0,0 +1,9 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +import "github.com/stephenafamo/bob" + +// Set the testDB to enable tests that use the database +var testDB bob.Transactor[bob.Tx] diff --git a/dberrors/goose_db_version.bob.go b/dberrors/goose_db_version.bob.go new file mode 100644 index 00000000..c3308b22 --- /dev/null +++ b/dberrors/goose_db_version.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var GooseDBVersionErrors = &gooseDBVersionErrors{ + ErrUniqueGooseDbVersionPkey: &UniqueConstraintError{ + schema: "", + table: "goose_db_version", + columns: []string{"id"}, + s: "goose_db_version_pkey", + }, +} + +type gooseDBVersionErrors struct { + ErrUniqueGooseDbVersionPkey *UniqueConstraintError +} diff --git a/dberrors/organization.bob.go b/dberrors/organization.bob.go new file mode 100644 index 00000000..2914c985 --- /dev/null +++ b/dberrors/organization.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var OrganizationErrors = &organizationErrors{ + ErrUniqueOrganizationPkey: &UniqueConstraintError{ + schema: "", + table: "organization", + columns: []string{"id"}, + s: "organization_pkey", + }, +} + +type organizationErrors struct { + ErrUniqueOrganizationPkey *UniqueConstraintError +} diff --git a/dberrors/user_.bob.go b/dberrors/user_.bob.go new file mode 100644 index 00000000..fb5945c6 --- /dev/null +++ b/dberrors/user_.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var UserErrors = &userErrors{ + ErrUniqueUser_Pkey: &UniqueConstraintError{ + schema: "", + table: "user_", + columns: []string{"id"}, + s: "user__pkey", + }, +} + +type userErrors struct { + ErrUniqueUser_Pkey *UniqueConstraintError +} diff --git a/dbinfo/bob_types.bob.go b/dbinfo/bob_types.bob.go new file mode 100644 index 00000000..138299c0 --- /dev/null +++ b/dbinfo/bob_types.bob.go @@ -0,0 +1,83 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dbinfo + +import "github.com/aarondl/opt/null" + +type Table[Cols columns, Idxs indexes, FKs foreignKeys, U uniques, C checks] struct { + Schema string + Name string + Columns Cols + Indexes Idxs + PrimaryKey *constraint + ForeignKeys FKs + Uniques U + Checks C + Comment string +} + +type columns interface { + AsSlice() []column +} + +type column struct { + Name string + DBType string + Default string + Comment string + Nullable bool + Generated bool + AutoIncr bool +} + +type indexes interface { + AsSlice() []index +} + +type index struct { + Type string + Name string + Columns []indexColumn + Unique bool + Comment string + NullsFirst []bool + NullsDistinct bool + Where string + Include []string +} + +type indexColumn struct { + Name string + Desc null.Val[bool] + IsExpression bool +} + +type constraint struct { + Name string + Columns []string + Comment string +} + +type foreignKeys interface { + AsSlice() []foreignKey +} + +type foreignKey struct { + constraint + ForeignTable string + ForeignColumns []string +} + +type uniques interface { + AsSlice() []constraint +} + +type checks interface { + AsSlice() []check +} + +type check struct { + constraint + Expression string +} diff --git a/dbinfo/goose_db_version.bob.go b/dbinfo/goose_db_version.bob.go new file mode 100644 index 00000000..b62e2e9c --- /dev/null +++ b/dbinfo/goose_db_version.bob.go @@ -0,0 +1,122 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dbinfo + +import "github.com/aarondl/opt/null" + +var GooseDBVersions = Table[ + gooseDBVersionColumns, + gooseDBVersionIndexes, + gooseDBVersionForeignKeys, + gooseDBVersionUniques, + gooseDBVersionChecks, +]{ + Schema: "", + Name: "goose_db_version", + Columns: gooseDBVersionColumns{ + ID: column{ + Name: "id", + DBType: "integer", + Default: "IDENTITY", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + VersionID: column{ + Name: "version_id", + DBType: "bigint", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + IsApplied: column{ + Name: "is_applied", + DBType: "boolean", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Tstamp: column{ + Name: "tstamp", + DBType: "timestamp without time zone", + Default: "now()", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: gooseDBVersionIndexes{ + GooseDBVersionPkey: index{ + Type: "btree", + Name: "goose_db_version_pkey", + Columns: []indexColumn{ + { + Name: "id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "goose_db_version_pkey", + Columns: []string{"id"}, + Comment: "", + }, + + Comment: "", +} + +type gooseDBVersionColumns struct { + ID column + VersionID column + IsApplied column + Tstamp column +} + +func (c gooseDBVersionColumns) AsSlice() []column { + return []column{ + c.ID, c.VersionID, c.IsApplied, c.Tstamp, + } +} + +type gooseDBVersionIndexes struct { + GooseDBVersionPkey index +} + +func (i gooseDBVersionIndexes) AsSlice() []index { + return []index{ + i.GooseDBVersionPkey, + } +} + +type gooseDBVersionForeignKeys struct{} + +func (f gooseDBVersionForeignKeys) AsSlice() []foreignKey { + return []foreignKey{} +} + +type gooseDBVersionUniques struct{} + +func (u gooseDBVersionUniques) AsSlice() []constraint { + return []constraint{} +} + +type gooseDBVersionChecks struct{} + +func (c gooseDBVersionChecks) AsSlice() []check { + return []check{} +} diff --git a/dbinfo/organization.bob.go b/dbinfo/organization.bob.go new file mode 100644 index 00000000..77516633 --- /dev/null +++ b/dbinfo/organization.bob.go @@ -0,0 +1,102 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dbinfo + +import "github.com/aarondl/opt/null" + +var Organizations = Table[ + organizationColumns, + organizationIndexes, + organizationForeignKeys, + organizationUniques, + organizationChecks, +]{ + Schema: "", + Name: "organization", + Columns: organizationColumns{ + ID: column{ + Name: "id", + DBType: "integer", + Default: "nextval('organization_id_seq'::regclass)", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Name: column{ + Name: "name", + DBType: "text", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: organizationIndexes{ + OrganizationPkey: index{ + Type: "btree", + Name: "organization_pkey", + Columns: []indexColumn{ + { + Name: "id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "organization_pkey", + Columns: []string{"id"}, + Comment: "", + }, + + Comment: "", +} + +type organizationColumns struct { + ID column + Name column +} + +func (c organizationColumns) AsSlice() []column { + return []column{ + c.ID, c.Name, + } +} + +type organizationIndexes struct { + OrganizationPkey index +} + +func (i organizationIndexes) AsSlice() []index { + return []index{ + i.OrganizationPkey, + } +} + +type organizationForeignKeys struct{} + +func (f organizationForeignKeys) AsSlice() []foreignKey { + return []foreignKey{} +} + +type organizationUniques struct{} + +func (u organizationUniques) AsSlice() []constraint { + return []constraint{} +} + +type organizationChecks struct{} + +func (c organizationChecks) AsSlice() []check { + return []check{} +} diff --git a/dbinfo/user_.bob.go b/dbinfo/user_.bob.go new file mode 100644 index 00000000..10c9aff2 --- /dev/null +++ b/dbinfo/user_.bob.go @@ -0,0 +1,197 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dbinfo + +import "github.com/aarondl/opt/null" + +var Users = Table[ + userColumns, + userIndexes, + userForeignKeys, + userUniques, + userChecks, +]{ + Schema: "", + Name: "user_", + Columns: userColumns{ + ID: column{ + Name: "id", + DBType: "integer", + Default: "nextval('user__id_seq'::regclass)", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + ArcgisAccessToken: column{ + Name: "arcgis_access_token", + DBType: "text", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + ArcgisLicense: column{ + Name: "arcgis_license", + DBType: "public.arcgis_license_type", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + ArcgisRefreshToken: column{ + Name: "arcgis_refresh_token", + DBType: "text", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + ArcgisRefreshTokenExpires: column{ + Name: "arcgis_refresh_token_expires", + DBType: "timestamp without time zone", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + ArcgisRole: column{ + Name: "arcgis_role", + DBType: "text", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + DisplayName: column{ + Name: "display_name", + DBType: "character varying", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + Email: column{ + Name: "email", + DBType: "text", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + OrganizationID: column{ + Name: "organization_id", + DBType: "integer", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + Username: column{ + Name: "username", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: userIndexes{ + UserPkey: index{ + Type: "btree", + Name: "user__pkey", + Columns: []indexColumn{ + { + Name: "id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "user__pkey", + Columns: []string{"id"}, + Comment: "", + }, + ForeignKeys: userForeignKeys{ + UserUserOrganizationIDFkey: foreignKey{ + constraint: constraint{ + Name: "user_.user__organization_id_fkey", + Columns: []string{"organization_id"}, + Comment: "", + }, + ForeignTable: "organization", + ForeignColumns: []string{"id"}, + }, + }, + + Comment: "", +} + +type userColumns struct { + ID column + ArcgisAccessToken column + ArcgisLicense column + ArcgisRefreshToken column + ArcgisRefreshTokenExpires column + ArcgisRole column + DisplayName column + Email column + OrganizationID column + Username column +} + +func (c userColumns) AsSlice() []column { + return []column{ + c.ID, c.ArcgisAccessToken, c.ArcgisLicense, c.ArcgisRefreshToken, c.ArcgisRefreshTokenExpires, c.ArcgisRole, c.DisplayName, c.Email, c.OrganizationID, c.Username, + } +} + +type userIndexes struct { + UserPkey index +} + +func (i userIndexes) AsSlice() []index { + return []index{ + i.UserPkey, + } +} + +type userForeignKeys struct { + UserUserOrganizationIDFkey foreignKey +} + +func (f userForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.UserUserOrganizationIDFkey, + } +} + +type userUniques struct{} + +func (u userUniques) AsSlice() []constraint { + return []constraint{} +} + +type userChecks struct{} + +func (c userChecks) AsSlice() []check { + return []check{} +} diff --git a/enums/enums.bob.go b/enums/enums.bob.go new file mode 100644 index 00000000..d294026d --- /dev/null +++ b/enums/enums.bob.go @@ -0,0 +1,118 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package enums + +import ( + "database/sql/driver" + "fmt" +) + +// Enum values for ArcgisLicenseType +const ( + ArcgisLicenseTypeAdvancedut ArcgisLicenseType = "advancedUT" + ArcgisLicenseTypeBasicut ArcgisLicenseType = "basicUT" + ArcgisLicenseTypeCreatorut ArcgisLicenseType = "creatorUT" + ArcgisLicenseTypeEditorut ArcgisLicenseType = "editorUT" + ArcgisLicenseTypeFieldworkerut ArcgisLicenseType = "fieldWorkerUT" + ArcgisLicenseTypeGisprofessionaladvut ArcgisLicenseType = "GISProfessionalAdvUT" + ArcgisLicenseTypeGisprofessionalbasicut ArcgisLicenseType = "GISProfessionalBasicUT" + ArcgisLicenseTypeGisprofessionalstdut ArcgisLicenseType = "GISProfessionalStdUT" + ArcgisLicenseTypeIndoorsuserut ArcgisLicenseType = "IndoorsUserUT" + ArcgisLicenseTypeInsightsanalystut ArcgisLicenseType = "insightsAnalystUT" + ArcgisLicenseTypeLiteut ArcgisLicenseType = "liteUT" + ArcgisLicenseTypeStandardut ArcgisLicenseType = "standardUT" + ArcgisLicenseTypeStorytellerut ArcgisLicenseType = "storytellerUT" + ArcgisLicenseTypeViewerut ArcgisLicenseType = "viewerUT" +) + +func AllArcgisLicenseType() []ArcgisLicenseType { + return []ArcgisLicenseType{ + ArcgisLicenseTypeAdvancedut, + ArcgisLicenseTypeBasicut, + ArcgisLicenseTypeCreatorut, + ArcgisLicenseTypeEditorut, + ArcgisLicenseTypeFieldworkerut, + ArcgisLicenseTypeGisprofessionaladvut, + ArcgisLicenseTypeGisprofessionalbasicut, + ArcgisLicenseTypeGisprofessionalstdut, + ArcgisLicenseTypeIndoorsuserut, + ArcgisLicenseTypeInsightsanalystut, + ArcgisLicenseTypeLiteut, + ArcgisLicenseTypeStandardut, + ArcgisLicenseTypeStorytellerut, + ArcgisLicenseTypeViewerut, + } +} + +type ArcgisLicenseType string + +func (e ArcgisLicenseType) String() string { + return string(e) +} + +func (e ArcgisLicenseType) Valid() bool { + switch e { + case ArcgisLicenseTypeAdvancedut, + ArcgisLicenseTypeBasicut, + ArcgisLicenseTypeCreatorut, + ArcgisLicenseTypeEditorut, + ArcgisLicenseTypeFieldworkerut, + ArcgisLicenseTypeGisprofessionaladvut, + ArcgisLicenseTypeGisprofessionalbasicut, + ArcgisLicenseTypeGisprofessionalstdut, + ArcgisLicenseTypeIndoorsuserut, + ArcgisLicenseTypeInsightsanalystut, + ArcgisLicenseTypeLiteut, + ArcgisLicenseTypeStandardut, + ArcgisLicenseTypeStorytellerut, + ArcgisLicenseTypeViewerut: + return true + default: + return false + } +} + +// useful when testing in other packages +func (e ArcgisLicenseType) All() []ArcgisLicenseType { + return AllArcgisLicenseType() +} + +func (e ArcgisLicenseType) MarshalText() ([]byte, error) { + return []byte(e), nil +} + +func (e *ArcgisLicenseType) UnmarshalText(text []byte) error { + return e.Scan(text) +} + +func (e ArcgisLicenseType) MarshalBinary() ([]byte, error) { + return []byte(e), nil +} + +func (e *ArcgisLicenseType) UnmarshalBinary(data []byte) error { + return e.Scan(data) +} + +func (e ArcgisLicenseType) Value() (driver.Value, error) { + return string(e), nil +} + +func (e *ArcgisLicenseType) Scan(value any) error { + switch x := value.(type) { + case string: + *e = ArcgisLicenseType(x) + case []byte: + *e = ArcgisLicenseType(x) + case nil: + return fmt.Errorf("cannot nil into ArcgisLicenseType") + default: + return fmt.Errorf("cannot scan type %T: %v", value, value) + } + + if !e.Valid() { + return fmt.Errorf("invalid ArcgisLicenseType value: %s", *e) + } + + return nil +} diff --git a/factory/bobfactory_context.bob.go b/factory/bobfactory_context.bob.go new file mode 100644 index 00000000..631bc77b --- /dev/null +++ b/factory/bobfactory_context.bob.go @@ -0,0 +1,39 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import "context" + +type contextKey string + +var ( + // Relationship Contexts for goose_db_version + gooseDBVersionWithParentsCascadingCtx = newContextual[bool]("gooseDBVersionWithParentsCascading") + + // Relationship Contexts for organization + organizationWithParentsCascadingCtx = newContextual[bool]("organizationWithParentsCascading") + organizationRelUserCtx = newContextual[bool]("organization.user_.user_.user__organization_id_fkey") + + // Relationship Contexts for user_ + userWithParentsCascadingCtx = newContextual[bool]("userWithParentsCascading") + userRelOrganizationCtx = newContextual[bool]("organization.user_.user_.user__organization_id_fkey") +) + +// Contextual is a convienience wrapper around context.WithValue and context.Value +type contextual[V any] struct { + key contextKey +} + +func newContextual[V any](key string) contextual[V] { + return contextual[V]{key: contextKey(key)} +} + +func (k contextual[V]) WithValue(ctx context.Context, val V) context.Context { + return context.WithValue(ctx, k.key, val) +} + +func (k contextual[V]) Value(ctx context.Context) (V, bool) { + v, ok := ctx.Value(k.key).(V) + return v, ok +} diff --git a/factory/bobfactory_main.bob.go b/factory/bobfactory_main.bob.go new file mode 100644 index 00000000..c9e1dd62 --- /dev/null +++ b/factory/bobfactory_main.bob.go @@ -0,0 +1,142 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/enums" + models "github.com/Gleipnir-Technology/nidus-sync/models" + "github.com/aarondl/opt/null" +) + +type Factory struct { + baseGooseDBVersionMods GooseDBVersionModSlice + baseOrganizationMods OrganizationModSlice + baseUserMods UserModSlice +} + +func New() *Factory { + return &Factory{} +} + +func (f *Factory) NewGooseDBVersion(mods ...GooseDBVersionMod) *GooseDBVersionTemplate { + return f.NewGooseDBVersionWithContext(context.Background(), mods...) +} + +func (f *Factory) NewGooseDBVersionWithContext(ctx context.Context, mods ...GooseDBVersionMod) *GooseDBVersionTemplate { + o := &GooseDBVersionTemplate{f: f} + + if f != nil { + f.baseGooseDBVersionMods.Apply(ctx, o) + } + + GooseDBVersionModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingGooseDBVersion(m *models.GooseDBVersion) *GooseDBVersionTemplate { + o := &GooseDBVersionTemplate{f: f, alreadyPersisted: true} + + o.ID = func() int32 { return m.ID } + o.VersionID = func() int64 { return m.VersionID } + o.IsApplied = func() bool { return m.IsApplied } + o.Tstamp = func() time.Time { return m.Tstamp } + + return o +} + +func (f *Factory) NewOrganization(mods ...OrganizationMod) *OrganizationTemplate { + return f.NewOrganizationWithContext(context.Background(), mods...) +} + +func (f *Factory) NewOrganizationWithContext(ctx context.Context, mods ...OrganizationMod) *OrganizationTemplate { + o := &OrganizationTemplate{f: f} + + if f != nil { + f.baseOrganizationMods.Apply(ctx, o) + } + + OrganizationModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingOrganization(m *models.Organization) *OrganizationTemplate { + o := &OrganizationTemplate{f: f, alreadyPersisted: true} + + o.ID = func() int32 { return m.ID } + o.Name = func() null.Val[string] { return m.Name } + + ctx := context.Background() + if len(m.R.User) > 0 { + OrganizationMods.AddExistingUser(m.R.User...).Apply(ctx, o) + } + + return o +} + +func (f *Factory) NewUser(mods ...UserMod) *UserTemplate { + return f.NewUserWithContext(context.Background(), mods...) +} + +func (f *Factory) NewUserWithContext(ctx context.Context, mods ...UserMod) *UserTemplate { + o := &UserTemplate{f: f} + + if f != nil { + f.baseUserMods.Apply(ctx, o) + } + + UserModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingUser(m *models.User) *UserTemplate { + o := &UserTemplate{f: f, alreadyPersisted: true} + + o.ID = func() int32 { return m.ID } + o.ArcgisAccessToken = func() null.Val[string] { return m.ArcgisAccessToken } + o.ArcgisLicense = func() null.Val[enums.ArcgisLicenseType] { return m.ArcgisLicense } + o.ArcgisRefreshToken = func() null.Val[string] { return m.ArcgisRefreshToken } + o.ArcgisRefreshTokenExpires = func() null.Val[time.Time] { return m.ArcgisRefreshTokenExpires } + o.ArcgisRole = func() null.Val[string] { return m.ArcgisRole } + o.DisplayName = func() null.Val[string] { return m.DisplayName } + o.Email = func() null.Val[string] { return m.Email } + o.OrganizationID = func() null.Val[int32] { return m.OrganizationID } + o.Username = func() string { return m.Username } + + ctx := context.Background() + if m.R.Organization != nil { + UserMods.WithExistingOrganization(m.R.Organization).Apply(ctx, o) + } + + return o +} + +func (f *Factory) ClearBaseGooseDBVersionMods() { + f.baseGooseDBVersionMods = nil +} + +func (f *Factory) AddBaseGooseDBVersionMod(mods ...GooseDBVersionMod) { + f.baseGooseDBVersionMods = append(f.baseGooseDBVersionMods, mods...) +} + +func (f *Factory) ClearBaseOrganizationMods() { + f.baseOrganizationMods = nil +} + +func (f *Factory) AddBaseOrganizationMod(mods ...OrganizationMod) { + f.baseOrganizationMods = append(f.baseOrganizationMods, mods...) +} + +func (f *Factory) ClearBaseUserMods() { + f.baseUserMods = nil +} + +func (f *Factory) AddBaseUserMod(mods ...UserMod) { + f.baseUserMods = append(f.baseUserMods, mods...) +} diff --git a/factory/bobfactory_main.bob_test.go b/factory/bobfactory_main.bob_test.go new file mode 100644 index 00000000..51a5a23e --- /dev/null +++ b/factory/bobfactory_main.bob_test.go @@ -0,0 +1,81 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" +) + +func TestCreateGooseDBVersion(t *testing.T) { + if testDB == nil { + t.Skip("skipping test, no DSN provided") + } + + ctx, cancel := context.WithCancel(t.Context()) + t.Cleanup(cancel) + + tx, err := testDB.Begin(ctx) + if err != nil { + t.Fatalf("Error starting transaction: %v", err) + } + + defer func() { + if err := tx.Rollback(ctx); err != nil { + t.Fatalf("Error rolling back transaction: %v", err) + } + }() + + if _, err := New().NewGooseDBVersionWithContext(ctx).Create(ctx, tx); err != nil { + t.Fatalf("Error creating GooseDBVersion: %v", err) + } +} + +func TestCreateOrganization(t *testing.T) { + if testDB == nil { + t.Skip("skipping test, no DSN provided") + } + + ctx, cancel := context.WithCancel(t.Context()) + t.Cleanup(cancel) + + tx, err := testDB.Begin(ctx) + if err != nil { + t.Fatalf("Error starting transaction: %v", err) + } + + defer func() { + if err := tx.Rollback(ctx); err != nil { + t.Fatalf("Error rolling back transaction: %v", err) + } + }() + + if _, err := New().NewOrganizationWithContext(ctx).Create(ctx, tx); err != nil { + t.Fatalf("Error creating Organization: %v", err) + } +} + +func TestCreateUser(t *testing.T) { + if testDB == nil { + t.Skip("skipping test, no DSN provided") + } + + ctx, cancel := context.WithCancel(t.Context()) + t.Cleanup(cancel) + + tx, err := testDB.Begin(ctx) + if err != nil { + t.Fatalf("Error starting transaction: %v", err) + } + + defer func() { + if err := tx.Rollback(ctx); err != nil { + t.Fatalf("Error rolling back transaction: %v", err) + } + }() + + if _, err := New().NewUserWithContext(ctx).Create(ctx, tx); err != nil { + t.Fatalf("Error creating User: %v", err) + } +} diff --git a/factory/bobfactory_random.bob.go b/factory/bobfactory_random.bob.go new file mode 100644 index 00000000..e4fb8bcc --- /dev/null +++ b/factory/bobfactory_random.bob.go @@ -0,0 +1,76 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "strconv" + "strings" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/enums" + "github.com/jaswdr/faker/v2" +) + +var defaultFaker = faker.New() + +func random_bool(f *faker.Faker, limits ...string) bool { + if f == nil { + f = &defaultFaker + } + + return f.Bool() +} + +func random_enums_ArcgisLicenseType(f *faker.Faker, limits ...string) enums.ArcgisLicenseType { + if f == nil { + f = &defaultFaker + } + + var e enums.ArcgisLicenseType + all := e.All() + return all[f.IntBetween(0, len(all)-1)] +} + +func random_int32(f *faker.Faker, limits ...string) int32 { + if f == nil { + f = &defaultFaker + } + + return f.Int32() +} + +func random_int64(f *faker.Faker, limits ...string) int64 { + if f == nil { + f = &defaultFaker + } + + return f.Int64() +} + +func random_string(f *faker.Faker, limits ...string) string { + if f == nil { + f = &defaultFaker + } + + val := strings.Join(f.Lorem().Words(f.IntBetween(1, 5)), " ") + if len(limits) == 0 { + return val + } + limitInt, _ := strconv.Atoi(limits[0]) + if limitInt > 0 && limitInt < len(val) { + val = val[:limitInt] + } + return val +} + +func random_time_Time(f *faker.Faker, limits ...string) time.Time { + if f == nil { + f = &defaultFaker + } + + year := time.Hour * 24 * 365 + min := time.Now().Add(-year) + max := time.Now().Add(year) + return f.Time().TimeBetween(min, max) +} diff --git a/factory/bobfactory_random.bob_test.go b/factory/bobfactory_random.bob_test.go new file mode 100644 index 00000000..112fcf04 --- /dev/null +++ b/factory/bobfactory_random.bob_test.go @@ -0,0 +1,57 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "testing" + + "github.com/stephenafamo/bob" +) + +// Set the testDB to enable tests that use the database +var testDB bob.Transactor[bob.Tx] + +func TestRandom_int32(t *testing.T) { + t.Parallel() + + val1 := random_int32(nil) + val2 := random_int32(nil) + + if val1 == val2 { + t.Fatalf("random_int32() returned the same value twice: %v", val1) + } +} + +func TestRandom_int64(t *testing.T) { + t.Parallel() + + val1 := random_int64(nil) + val2 := random_int64(nil) + + if val1 == val2 { + t.Fatalf("random_int64() returned the same value twice: %v", val1) + } +} + +func TestRandom_string(t *testing.T) { + t.Parallel() + + val1 := random_string(nil) + val2 := random_string(nil) + + if val1 == val2 { + t.Fatalf("random_string() returned the same value twice: %v", val1) + } +} + +func TestRandom_time_Time(t *testing.T) { + t.Parallel() + + val1 := random_time_Time(nil) + val2 := random_time_Time(nil) + + if val1.Equal(val2) { + t.Fatalf("random_time_Time() returned the same value twice: %v", val1) + } +} diff --git a/factory/goose_db_version.bob.go b/factory/goose_db_version.bob.go new file mode 100644 index 00000000..27a64162 --- /dev/null +++ b/factory/goose_db_version.bob.go @@ -0,0 +1,380 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + "time" + + models "github.com/Gleipnir-Technology/nidus-sync/models" + "github.com/aarondl/opt/omit" + "github.com/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type GooseDBVersionMod interface { + Apply(context.Context, *GooseDBVersionTemplate) +} + +type GooseDBVersionModFunc func(context.Context, *GooseDBVersionTemplate) + +func (f GooseDBVersionModFunc) Apply(ctx context.Context, n *GooseDBVersionTemplate) { + f(ctx, n) +} + +type GooseDBVersionModSlice []GooseDBVersionMod + +func (mods GooseDBVersionModSlice) Apply(ctx context.Context, n *GooseDBVersionTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// GooseDBVersionTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type GooseDBVersionTemplate struct { + ID func() int32 + VersionID func() int64 + IsApplied func() bool + Tstamp func() time.Time + + f *Factory + + alreadyPersisted bool +} + +// Apply mods to the GooseDBVersionTemplate +func (o *GooseDBVersionTemplate) Apply(ctx context.Context, mods ...GooseDBVersionMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.GooseDBVersion +// according to the relationships in the template. Nothing is inserted into the db +func (t GooseDBVersionTemplate) setModelRels(o *models.GooseDBVersion) {} + +// BuildSetter returns an *models.GooseDBVersionSetter +// this does nothing with the relationship templates +func (o GooseDBVersionTemplate) BuildSetter() *models.GooseDBVersionSetter { + m := &models.GooseDBVersionSetter{} + + if o.ID != nil { + val := o.ID() + m.ID = omit.From(val) + } + if o.VersionID != nil { + val := o.VersionID() + m.VersionID = omit.From(val) + } + if o.IsApplied != nil { + val := o.IsApplied() + m.IsApplied = omit.From(val) + } + if o.Tstamp != nil { + val := o.Tstamp() + m.Tstamp = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.GooseDBVersionSetter +// this does nothing with the relationship templates +func (o GooseDBVersionTemplate) BuildManySetter(number int) []*models.GooseDBVersionSetter { + m := make([]*models.GooseDBVersionSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.GooseDBVersion +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use GooseDBVersionTemplate.Create +func (o GooseDBVersionTemplate) Build() *models.GooseDBVersion { + m := &models.GooseDBVersion{} + + if o.ID != nil { + m.ID = o.ID() + } + if o.VersionID != nil { + m.VersionID = o.VersionID() + } + if o.IsApplied != nil { + m.IsApplied = o.IsApplied() + } + if o.Tstamp != nil { + m.Tstamp = o.Tstamp() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.GooseDBVersionSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use GooseDBVersionTemplate.CreateMany +func (o GooseDBVersionTemplate) BuildMany(number int) models.GooseDBVersionSlice { + m := make(models.GooseDBVersionSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableGooseDBVersion(m *models.GooseDBVersionSetter) { + if !(m.VersionID.IsValue()) { + val := random_int64(nil) + m.VersionID = omit.From(val) + } + if !(m.IsApplied.IsValue()) { + val := random_bool(nil) + m.IsApplied = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.GooseDBVersion +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *GooseDBVersionTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.GooseDBVersion) error { + var err error + + return err +} + +// Create builds a gooseDBVersion and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *GooseDBVersionTemplate) Create(ctx context.Context, exec bob.Executor) (*models.GooseDBVersion, error) { + var err error + opt := o.BuildSetter() + ensureCreatableGooseDBVersion(opt) + + m, err := models.GooseDBVersions.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a gooseDBVersion and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *GooseDBVersionTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.GooseDBVersion { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a gooseDBVersion and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o *GooseDBVersionTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.GooseDBVersion { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple gooseDBVersions and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o GooseDBVersionTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.GooseDBVersionSlice, error) { + var err error + m := make(models.GooseDBVersionSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple gooseDBVersions and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o GooseDBVersionTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.GooseDBVersionSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple gooseDBVersions and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o GooseDBVersionTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.GooseDBVersionSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// GooseDBVersion has methods that act as mods for the GooseDBVersionTemplate +var GooseDBVersionMods gooseDBVersionMods + +type gooseDBVersionMods struct{} + +func (m gooseDBVersionMods) RandomizeAllColumns(f *faker.Faker) GooseDBVersionMod { + return GooseDBVersionModSlice{ + GooseDBVersionMods.RandomID(f), + GooseDBVersionMods.RandomVersionID(f), + GooseDBVersionMods.RandomIsApplied(f), + GooseDBVersionMods.RandomTstamp(f), + } +} + +// Set the model columns to this value +func (m gooseDBVersionMods) ID(val int32) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m gooseDBVersionMods) IDFunc(f func() int32) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m gooseDBVersionMods) UnsetID() GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.ID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m gooseDBVersionMods) RandomID(f *faker.Faker) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m gooseDBVersionMods) VersionID(val int64) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.VersionID = func() int64 { return val } + }) +} + +// Set the Column from the function +func (m gooseDBVersionMods) VersionIDFunc(f func() int64) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.VersionID = f + }) +} + +// Clear any values for the column +func (m gooseDBVersionMods) UnsetVersionID() GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.VersionID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m gooseDBVersionMods) RandomVersionID(f *faker.Faker) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.VersionID = func() int64 { + return random_int64(f) + } + }) +} + +// Set the model columns to this value +func (m gooseDBVersionMods) IsApplied(val bool) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.IsApplied = func() bool { return val } + }) +} + +// Set the Column from the function +func (m gooseDBVersionMods) IsAppliedFunc(f func() bool) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.IsApplied = f + }) +} + +// Clear any values for the column +func (m gooseDBVersionMods) UnsetIsApplied() GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.IsApplied = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m gooseDBVersionMods) RandomIsApplied(f *faker.Faker) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.IsApplied = func() bool { + return random_bool(f) + } + }) +} + +// Set the model columns to this value +func (m gooseDBVersionMods) Tstamp(val time.Time) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.Tstamp = func() time.Time { return val } + }) +} + +// Set the Column from the function +func (m gooseDBVersionMods) TstampFunc(f func() time.Time) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.Tstamp = f + }) +} + +// Clear any values for the column +func (m gooseDBVersionMods) UnsetTstamp() GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.Tstamp = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m gooseDBVersionMods) RandomTstamp(f *faker.Faker) GooseDBVersionMod { + return GooseDBVersionModFunc(func(_ context.Context, o *GooseDBVersionTemplate) { + o.Tstamp = func() time.Time { + return random_time_Time(f) + } + }) +} + +func (m gooseDBVersionMods) WithParentsCascading() GooseDBVersionMod { + return GooseDBVersionModFunc(func(ctx context.Context, o *GooseDBVersionTemplate) { + if isDone, _ := gooseDBVersionWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = gooseDBVersionWithParentsCascadingCtx.WithValue(ctx, true) + }) +} diff --git a/factory/organization.bob.go b/factory/organization.bob.go new file mode 100644 index 00000000..497959fc --- /dev/null +++ b/factory/organization.bob.go @@ -0,0 +1,406 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + + models "github.com/Gleipnir-Technology/nidus-sync/models" + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type OrganizationMod interface { + Apply(context.Context, *OrganizationTemplate) +} + +type OrganizationModFunc func(context.Context, *OrganizationTemplate) + +func (f OrganizationModFunc) Apply(ctx context.Context, n *OrganizationTemplate) { + f(ctx, n) +} + +type OrganizationModSlice []OrganizationMod + +func (mods OrganizationModSlice) Apply(ctx context.Context, n *OrganizationTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// OrganizationTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type OrganizationTemplate struct { + ID func() int32 + Name func() null.Val[string] + + r organizationR + f *Factory + + alreadyPersisted bool +} + +type organizationR struct { + User []*organizationRUserR +} + +type organizationRUserR struct { + number int + o *UserTemplate +} + +// Apply mods to the OrganizationTemplate +func (o *OrganizationTemplate) Apply(ctx context.Context, mods ...OrganizationMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.Organization +// according to the relationships in the template. Nothing is inserted into the db +func (t OrganizationTemplate) setModelRels(o *models.Organization) { + if t.r.User != nil { + rel := models.UserSlice{} + for _, r := range t.r.User { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.OrganizationID = null.From(o.ID) // h2 + rel.R.Organization = o + } + rel = append(rel, related...) + } + o.R.User = rel + } +} + +// BuildSetter returns an *models.OrganizationSetter +// this does nothing with the relationship templates +func (o OrganizationTemplate) BuildSetter() *models.OrganizationSetter { + m := &models.OrganizationSetter{} + + if o.ID != nil { + val := o.ID() + m.ID = omit.From(val) + } + if o.Name != nil { + val := o.Name() + m.Name = omitnull.FromNull(val) + } + + return m +} + +// BuildManySetter returns an []*models.OrganizationSetter +// this does nothing with the relationship templates +func (o OrganizationTemplate) BuildManySetter(number int) []*models.OrganizationSetter { + m := make([]*models.OrganizationSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.Organization +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use OrganizationTemplate.Create +func (o OrganizationTemplate) Build() *models.Organization { + m := &models.Organization{} + + if o.ID != nil { + m.ID = o.ID() + } + if o.Name != nil { + m.Name = o.Name() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.OrganizationSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use OrganizationTemplate.CreateMany +func (o OrganizationTemplate) BuildMany(number int) models.OrganizationSlice { + m := make(models.OrganizationSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableOrganization(m *models.OrganizationSetter) { +} + +// insertOptRels creates and inserts any optional the relationships on *models.Organization +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.Organization) error { + var err error + + isUserDone, _ := organizationRelUserCtx.Value(ctx) + if !isUserDone && o.r.User != nil { + ctx = organizationRelUserCtx.WithValue(ctx, true) + for _, r := range o.r.User { + if r.o.alreadyPersisted { + m.R.User = append(m.R.User, r.o.Build()) + } else { + rel0, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachUser(ctx, exec, rel0...) + if err != nil { + return err + } + } + } + } + + return err +} + +// Create builds a organization and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *OrganizationTemplate) Create(ctx context.Context, exec bob.Executor) (*models.Organization, error) { + var err error + opt := o.BuildSetter() + ensureCreatableOrganization(opt) + + m, err := models.Organizations.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a organization and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *OrganizationTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.Organization { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a organization and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o *OrganizationTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.Organization { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple organizations and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o OrganizationTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.OrganizationSlice, error) { + var err error + m := make(models.OrganizationSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple organizations and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o OrganizationTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.OrganizationSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple organizations and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o OrganizationTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.OrganizationSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// Organization has methods that act as mods for the OrganizationTemplate +var OrganizationMods organizationMods + +type organizationMods struct{} + +func (m organizationMods) RandomizeAllColumns(f *faker.Faker) OrganizationMod { + return OrganizationModSlice{ + OrganizationMods.RandomID(f), + OrganizationMods.RandomName(f), + } +} + +// Set the model columns to this value +func (m organizationMods) ID(val int32) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m organizationMods) IDFunc(f func() int32) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m organizationMods) UnsetID() OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.ID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m organizationMods) RandomID(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m organizationMods) Name(val null.Val[string]) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Name = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m organizationMods) NameFunc(f func() null.Val[string]) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Name = f + }) +} + +// Clear any values for the column +func (m organizationMods) UnsetName() OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Name = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m organizationMods) RandomName(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Name = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m organizationMods) RandomNameNotNull(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Name = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +func (m organizationMods) WithParentsCascading() OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + if isDone, _ := organizationWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = organizationWithParentsCascadingCtx.WithValue(ctx, true) + }) +} + +func (m organizationMods) WithUser(number int, related *UserTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.User = []*organizationRUserR{{ + number: number, + o: related, + }} + }) +} + +func (m organizationMods) WithNewUser(number int, mods ...UserMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewUserWithContext(ctx, mods...) + m.WithUser(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddUser(number int, related *UserTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.User = append(o.r.User, &organizationRUserR{ + number: number, + o: related, + }) + }) +} + +func (m organizationMods) AddNewUser(number int, mods ...UserMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewUserWithContext(ctx, mods...) + m.AddUser(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddExistingUser(existingModels ...*models.User) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + for _, em := range existingModels { + o.r.User = append(o.r.User, &organizationRUserR{ + o: o.f.FromExistingUser(em), + }) + } + }) +} + +func (m organizationMods) WithoutUser() OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.User = nil + }) +} diff --git a/factory/user_.bob.go b/factory/user_.bob.go new file mode 100644 index 00000000..19071aec --- /dev/null +++ b/factory/user_.bob.go @@ -0,0 +1,865 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/enums" + models "github.com/Gleipnir-Technology/nidus-sync/models" + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type UserMod interface { + Apply(context.Context, *UserTemplate) +} + +type UserModFunc func(context.Context, *UserTemplate) + +func (f UserModFunc) Apply(ctx context.Context, n *UserTemplate) { + f(ctx, n) +} + +type UserModSlice []UserMod + +func (mods UserModSlice) Apply(ctx context.Context, n *UserTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// UserTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type UserTemplate struct { + ID func() int32 + ArcgisAccessToken func() null.Val[string] + ArcgisLicense func() null.Val[enums.ArcgisLicenseType] + ArcgisRefreshToken func() null.Val[string] + ArcgisRefreshTokenExpires func() null.Val[time.Time] + ArcgisRole func() null.Val[string] + DisplayName func() null.Val[string] + Email func() null.Val[string] + OrganizationID func() null.Val[int32] + Username func() string + + r userR + f *Factory + + alreadyPersisted bool +} + +type userR struct { + Organization *userROrganizationR +} + +type userROrganizationR struct { + o *OrganizationTemplate +} + +// Apply mods to the UserTemplate +func (o *UserTemplate) Apply(ctx context.Context, mods ...UserMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.User +// according to the relationships in the template. Nothing is inserted into the db +func (t UserTemplate) setModelRels(o *models.User) { + if t.r.Organization != nil { + rel := t.r.Organization.o.Build() + rel.R.User = append(rel.R.User, o) + o.OrganizationID = null.From(rel.ID) // h2 + o.R.Organization = rel + } +} + +// BuildSetter returns an *models.UserSetter +// this does nothing with the relationship templates +func (o UserTemplate) BuildSetter() *models.UserSetter { + m := &models.UserSetter{} + + if o.ID != nil { + val := o.ID() + m.ID = omit.From(val) + } + if o.ArcgisAccessToken != nil { + val := o.ArcgisAccessToken() + m.ArcgisAccessToken = omitnull.FromNull(val) + } + if o.ArcgisLicense != nil { + val := o.ArcgisLicense() + m.ArcgisLicense = omitnull.FromNull(val) + } + if o.ArcgisRefreshToken != nil { + val := o.ArcgisRefreshToken() + m.ArcgisRefreshToken = omitnull.FromNull(val) + } + if o.ArcgisRefreshTokenExpires != nil { + val := o.ArcgisRefreshTokenExpires() + m.ArcgisRefreshTokenExpires = omitnull.FromNull(val) + } + if o.ArcgisRole != nil { + val := o.ArcgisRole() + m.ArcgisRole = omitnull.FromNull(val) + } + if o.DisplayName != nil { + val := o.DisplayName() + m.DisplayName = omitnull.FromNull(val) + } + if o.Email != nil { + val := o.Email() + m.Email = omitnull.FromNull(val) + } + if o.OrganizationID != nil { + val := o.OrganizationID() + m.OrganizationID = omitnull.FromNull(val) + } + if o.Username != nil { + val := o.Username() + m.Username = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.UserSetter +// this does nothing with the relationship templates +func (o UserTemplate) BuildManySetter(number int) []*models.UserSetter { + m := make([]*models.UserSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.User +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use UserTemplate.Create +func (o UserTemplate) Build() *models.User { + m := &models.User{} + + if o.ID != nil { + m.ID = o.ID() + } + if o.ArcgisAccessToken != nil { + m.ArcgisAccessToken = o.ArcgisAccessToken() + } + if o.ArcgisLicense != nil { + m.ArcgisLicense = o.ArcgisLicense() + } + if o.ArcgisRefreshToken != nil { + m.ArcgisRefreshToken = o.ArcgisRefreshToken() + } + if o.ArcgisRefreshTokenExpires != nil { + m.ArcgisRefreshTokenExpires = o.ArcgisRefreshTokenExpires() + } + if o.ArcgisRole != nil { + m.ArcgisRole = o.ArcgisRole() + } + if o.DisplayName != nil { + m.DisplayName = o.DisplayName() + } + if o.Email != nil { + m.Email = o.Email() + } + if o.OrganizationID != nil { + m.OrganizationID = o.OrganizationID() + } + if o.Username != nil { + m.Username = o.Username() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.UserSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use UserTemplate.CreateMany +func (o UserTemplate) BuildMany(number int) models.UserSlice { + m := make(models.UserSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableUser(m *models.UserSetter) { + if !(m.Username.IsValue()) { + val := random_string(nil) + m.Username = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.User +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *UserTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.User) error { + var err error + + isOrganizationDone, _ := userRelOrganizationCtx.Value(ctx) + if !isOrganizationDone && o.r.Organization != nil { + ctx = userRelOrganizationCtx.WithValue(ctx, true) + if o.r.Organization.o.alreadyPersisted { + m.R.Organization = o.r.Organization.o.Build() + } else { + var rel0 *models.Organization + rel0, err = o.r.Organization.o.Create(ctx, exec) + if err != nil { + return err + } + err = m.AttachOrganization(ctx, exec, rel0) + if err != nil { + return err + } + } + + } + + return err +} + +// Create builds a user and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *UserTemplate) Create(ctx context.Context, exec bob.Executor) (*models.User, error) { + var err error + opt := o.BuildSetter() + ensureCreatableUser(opt) + + m, err := models.Users.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a user and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *UserTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.User { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a user and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o *UserTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.User { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple users and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o UserTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.UserSlice, error) { + var err error + m := make(models.UserSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple users and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o UserTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.UserSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple users and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs +func (o UserTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.UserSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// User has methods that act as mods for the UserTemplate +var UserMods userMods + +type userMods struct{} + +func (m userMods) RandomizeAllColumns(f *faker.Faker) UserMod { + return UserModSlice{ + UserMods.RandomID(f), + UserMods.RandomArcgisAccessToken(f), + UserMods.RandomArcgisLicense(f), + UserMods.RandomArcgisRefreshToken(f), + UserMods.RandomArcgisRefreshTokenExpires(f), + UserMods.RandomArcgisRole(f), + UserMods.RandomDisplayName(f), + UserMods.RandomEmail(f), + UserMods.RandomOrganizationID(f), + UserMods.RandomUsername(f), + } +} + +// Set the model columns to this value +func (m userMods) ID(val int32) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m userMods) IDFunc(f func() int32) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetID() UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m userMods) RandomID(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m userMods) ArcgisAccessToken(val null.Val[string]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisAccessToken = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m userMods) ArcgisAccessTokenFunc(f func() null.Val[string]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisAccessToken = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetArcgisAccessToken() UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisAccessToken = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m userMods) RandomArcgisAccessToken(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisAccessToken = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m userMods) RandomArcgisAccessTokenNotNull(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisAccessToken = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m userMods) ArcgisLicense(val null.Val[enums.ArcgisLicenseType]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisLicense = func() null.Val[enums.ArcgisLicenseType] { return val } + }) +} + +// Set the Column from the function +func (m userMods) ArcgisLicenseFunc(f func() null.Val[enums.ArcgisLicenseType]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisLicense = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetArcgisLicense() UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisLicense = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m userMods) RandomArcgisLicense(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisLicense = func() null.Val[enums.ArcgisLicenseType] { + if f == nil { + f = &defaultFaker + } + + val := random_enums_ArcgisLicenseType(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m userMods) RandomArcgisLicenseNotNull(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisLicense = func() null.Val[enums.ArcgisLicenseType] { + if f == nil { + f = &defaultFaker + } + + val := random_enums_ArcgisLicenseType(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m userMods) ArcgisRefreshToken(val null.Val[string]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRefreshToken = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m userMods) ArcgisRefreshTokenFunc(f func() null.Val[string]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRefreshToken = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetArcgisRefreshToken() UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRefreshToken = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m userMods) RandomArcgisRefreshToken(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRefreshToken = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m userMods) RandomArcgisRefreshTokenNotNull(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRefreshToken = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m userMods) ArcgisRefreshTokenExpires(val null.Val[time.Time]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRefreshTokenExpires = func() null.Val[time.Time] { return val } + }) +} + +// Set the Column from the function +func (m userMods) ArcgisRefreshTokenExpiresFunc(f func() null.Val[time.Time]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRefreshTokenExpires = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetArcgisRefreshTokenExpires() UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRefreshTokenExpires = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m userMods) RandomArcgisRefreshTokenExpires(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRefreshTokenExpires = func() null.Val[time.Time] { + if f == nil { + f = &defaultFaker + } + + val := random_time_Time(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m userMods) RandomArcgisRefreshTokenExpiresNotNull(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRefreshTokenExpires = func() null.Val[time.Time] { + if f == nil { + f = &defaultFaker + } + + val := random_time_Time(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m userMods) ArcgisRole(val null.Val[string]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRole = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m userMods) ArcgisRoleFunc(f func() null.Val[string]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRole = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetArcgisRole() UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRole = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m userMods) RandomArcgisRole(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRole = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m userMods) RandomArcgisRoleNotNull(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.ArcgisRole = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m userMods) DisplayName(val null.Val[string]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.DisplayName = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m userMods) DisplayNameFunc(f func() null.Val[string]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.DisplayName = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetDisplayName() UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.DisplayName = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m userMods) RandomDisplayName(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.DisplayName = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f, "200") + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m userMods) RandomDisplayNameNotNull(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.DisplayName = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f, "200") + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m userMods) Email(val null.Val[string]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.Email = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m userMods) EmailFunc(f func() null.Val[string]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.Email = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetEmail() UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.Email = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m userMods) RandomEmail(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.Email = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m userMods) RandomEmailNotNull(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.Email = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m userMods) OrganizationID(val null.Val[int32]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.OrganizationID = func() null.Val[int32] { return val } + }) +} + +// Set the Column from the function +func (m userMods) OrganizationIDFunc(f func() null.Val[int32]) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.OrganizationID = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetOrganizationID() UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.OrganizationID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m userMods) RandomOrganizationID(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.OrganizationID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m userMods) RandomOrganizationIDNotNull(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.OrganizationID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m userMods) Username(val string) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.Username = func() string { return val } + }) +} + +// Set the Column from the function +func (m userMods) UsernameFunc(f func() string) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.Username = f + }) +} + +// Clear any values for the column +func (m userMods) UnsetUsername() UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.Username = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m userMods) RandomUsername(f *faker.Faker) UserMod { + return UserModFunc(func(_ context.Context, o *UserTemplate) { + o.Username = func() string { + return random_string(f) + } + }) +} + +func (m userMods) WithParentsCascading() UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + if isDone, _ := userWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = userWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewOrganizationWithContext(ctx, OrganizationMods.WithParentsCascading()) + m.WithOrganization(related).Apply(ctx, o) + } + }) +} + +func (m userMods) WithOrganization(rel *OrganizationTemplate) UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + o.r.Organization = &userROrganizationR{ + o: rel, + } + }) +} + +func (m userMods) WithNewOrganization(mods ...OrganizationMod) UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + + m.WithOrganization(related).Apply(ctx, o) + }) +} + +func (m userMods) WithExistingOrganization(em *models.Organization) UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + o.r.Organization = &userROrganizationR{ + o: o.f.FromExistingOrganization(em), + } + }) +} + +func (m userMods) WithoutOrganization() UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + o.r.Organization = nil + }) +} diff --git a/go.mod b/go.mod index 77f59ad7..1e5fbc5c 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,21 @@ go 1.24.9 require ( github.com/alexedwards/scs/v2 v2.9.0 github.com/go-chi/chi/v5 v5.2.3 + github.com/jackc/pgx/v5 v5.7.6 + github.com/pressly/goose/v3 v3.26.0 + github.com/stephenafamo/bob v0.41.1 +) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/stephenafamo/scan v0.7.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/text v0.27.0 // indirect ) diff --git a/go.sum b/go.sum index 428aef0e..0b6ca6cb 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,76 @@ +github.com/aarondl/opt v0.0.0-20250607033636-982744e1bd65 h1:lbdPe4LBNmNDzeQFwNhEc88w90841qv737MI4+aXSYU= +github.com/aarondl/opt v0.0.0-20250607033636-982744e1bd65/go.mod h1:+xKBXrTAUOvrDXO5PRwIr4E1wciHY3Glgl+6OkCXknU= github.com/alexedwards/scs/v2 v2.9.0 h1:xa05mVpwTBm1iLeTMNFfAWpKUm4fXAW7CeAViqBVS90= github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= +github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= +github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= +github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmYrbRiUgv+g37W5suLLLxwwniTSc= +github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/stephenafamo/bob v0.41.1 h1:xcRPuRMCwtZZ9tS4JIVbZ5Erdm5Dy5dIvbS5kivwPpA= +github.com/stephenafamo/bob v0.41.1/go.mod h1:8l55917DM36gF518Iz1MHjLds7KGAfkitJfxISYlth8= +github.com/stephenafamo/fakedb v0.0.0-20221230081958-0b86f816ed97 h1:XItoZNmhOih06TC02jK7l3wlpZ0XT/sPQYutDcGOQjg= +github.com/stephenafamo/fakedb v0.0.0-20221230081958-0b86f816ed97/go.mod h1:bM3Vmw1IakoaXocHmMIGgJFYob0vuK+CFWiJHQvz0jQ= +github.com/stephenafamo/scan v0.7.0 h1:lfFiD9H5+n4AdK3qNzXQjj2M3NfTOpmWBIA39NwB94c= +github.com/stephenafamo/scan v0.7.0/go.mod h1:FhIUJ8pLNyex36xGFiazDJJ5Xry0UkAi+RkWRrEcRMg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= +github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= diff --git a/main.go b/main.go index a9d5bea7..b3352f88 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "log" "net/http" "os" @@ -18,12 +19,12 @@ var BaseURL, ClientID, ClientSecret string func main() { ClientID = os.Getenv("ARCGIS_CLIENT_ID") if ClientID == "" { - log.Println("You must specify a non-empty CLIENT_ID") + log.Println("You must specify a non-empty ARCGIS_CLIENT_ID") os.Exit(1) } ClientSecret = os.Getenv("ARCGIS_CLIENT_SECRET") if ClientSecret == "" { - log.Println("You must specify a non-empty CLIENT_SECRET") + log.Println("You must specify a non-empty ARCGIS_CLIENT_SECRET") os.Exit(1) } BaseURL = os.Getenv("BASE_URL") @@ -35,8 +36,18 @@ func main() { if bind == "" { bind = ":9001" } + pg_dsn := os.Getenv("POSTGRES_DSN") + if pg_dsn == "" { + log.Println("You must specify a non-empty POSTGRES_DSN") + os.Exit(1) + } log.Println("Starting...") + err := initializeDatabase(context.TODO(), pg_dsn) + if err != nil { + log.Printf("Failed to connect to database: %v", err) + os.Exit(2) + } sessionManager = scs.New() sessionManager.Lifetime = 24 * time.Hour diff --git a/migrations/00001_initial.sql b/migrations/00001_initial.sql new file mode 100644 index 00000000..a3749187 --- /dev/null +++ b/migrations/00001_initial.sql @@ -0,0 +1,39 @@ +-- +goose Up +CREATE TYPE arcgis_license_type AS ENUM ( + 'advancedUT', + 'basicUT', + 'creatorUT', + 'editorUT', + 'fieldWorkerUT', + 'GISProfessionalAdvUT', + 'GISProfessionalBasicUT', + 'GISProfessionalStdUT', + 'IndoorsUserUT', + 'insightsAnalystUT', + 'liteUT', + 'standardUT', + 'storytellerUT', + 'viewerUT'); + +CREATE TABLE organization ( + id SERIAL PRIMARY KEY, + name TEXT +); + +CREATE TABLE user_ ( + id SERIAL PRIMARY KEY, + arcgis_access_token TEXT, + arcgis_license arcgis_license_type, + arcgis_refresh_token TEXT, + arcgis_refresh_token_expires TIMESTAMP, + arcgis_role TEXT, + display_name VARCHAR(200), + email TEXT, + organization_id INTEGER REFERENCES organization (id), + username TEXT NOT NULL +); + +-- +goose Down +DROP TABLE user_; +DROP TABLE organization; +DROP TYPE arcgis_license_type; diff --git a/models/bob_joins.bob.go b/models/bob_joins.bob.go new file mode 100644 index 00000000..7058198a --- /dev/null +++ b/models/bob_joins.bob.go @@ -0,0 +1,76 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "hash/maphash" + + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/clause" + "github.com/stephenafamo/bob/dialect/psql/dialect" +) + +var ( + SelectJoins = getJoins[*dialect.SelectQuery]() + UpdateJoins = getJoins[*dialect.UpdateQuery]() + DeleteJoins = getJoins[*dialect.DeleteQuery]() +) + +type joinSet[Q interface{ aliasedAs(string) Q }] struct { + InnerJoin Q + LeftJoin Q + RightJoin Q +} + +func (j joinSet[Q]) AliasedAs(alias string) joinSet[Q] { + return joinSet[Q]{ + InnerJoin: j.InnerJoin.aliasedAs(alias), + LeftJoin: j.LeftJoin.aliasedAs(alias), + RightJoin: j.RightJoin.aliasedAs(alias), + } +} + +type joins[Q dialect.Joinable] struct { + Organizations joinSet[organizationJoins[Q]] + Users joinSet[userJoins[Q]] +} + +func buildJoinSet[Q interface{ aliasedAs(string) Q }, C any, F func(C, string) Q](c C, f F) joinSet[Q] { + return joinSet[Q]{ + InnerJoin: f(c, clause.InnerJoin), + LeftJoin: f(c, clause.LeftJoin), + RightJoin: f(c, clause.RightJoin), + } +} + +func getJoins[Q dialect.Joinable]() joins[Q] { + return joins[Q]{ + Organizations: buildJoinSet[organizationJoins[Q]](Organizations.Columns, buildOrganizationJoins), + Users: buildJoinSet[userJoins[Q]](Users.Columns, buildUserJoins), + } +} + +type modAs[Q any, C interface{ AliasedAs(string) C }] struct { + c C + f func(C) bob.Mod[Q] +} + +func (m modAs[Q, C]) Apply(q Q) { + m.f(m.c).Apply(q) +} + +func (m modAs[Q, C]) AliasedAs(alias string) bob.Mod[Q] { + m.c = m.c.AliasedAs(alias) + return m +} + +func randInt() int64 { + out := int64(new(maphash.Hash).Sum64()) + + if out < 0 { + return -out % 10000 + } + + return out % 10000 +} diff --git a/models/bob_loaders.bob.go b/models/bob_loaders.bob.go new file mode 100644 index 00000000..13aaec41 --- /dev/null +++ b/models/bob_loaders.bob.go @@ -0,0 +1,67 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/dialect/psql/dialect" + "github.com/stephenafamo/bob/orm" +) + +var Preload = getPreloaders() + +type preloaders struct { + Organization organizationPreloader + User userPreloader +} + +func getPreloaders() preloaders { + return preloaders{ + Organization: buildOrganizationPreloader(), + User: buildUserPreloader(), + } +} + +var ( + SelectThenLoad = getThenLoaders[*dialect.SelectQuery]() + InsertThenLoad = getThenLoaders[*dialect.InsertQuery]() + UpdateThenLoad = getThenLoaders[*dialect.UpdateQuery]() +) + +type thenLoaders[Q orm.Loadable] struct { + Organization organizationThenLoader[Q] + User userThenLoader[Q] +} + +func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { + return thenLoaders[Q]{ + Organization: buildOrganizationThenLoader[Q](), + User: buildUserThenLoader[Q](), + } +} + +func thenLoadBuilder[Q orm.Loadable, T any](name string, f func(context.Context, bob.Executor, T, ...bob.Mod[*dialect.SelectQuery]) error) func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] { + return func(queryMods ...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] { + return func(ctx context.Context, exec bob.Executor, retrieved any) error { + loader, isLoader := retrieved.(T) + if !isLoader { + return fmt.Errorf("object %T cannot load %q", retrieved, name) + } + + err := f(ctx, exec, loader, queryMods...) + + // Don't cause an issue due to missing relationships + if errors.Is(err, sql.ErrNoRows) { + return nil + } + + return err + } + } +} diff --git a/models/bob_types.bob_test.go b/models/bob_types.bob_test.go new file mode 100644 index 00000000..b58bc40f --- /dev/null +++ b/models/bob_types.bob_test.go @@ -0,0 +1,30 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "database/sql" + "database/sql/driver" + + enums "github.com/Gleipnir-Technology/nidus-sync/enums" + "github.com/stephenafamo/bob" +) + +// Set the testDB to enable tests that use the database +var testDB bob.Transactor[bob.Tx] + +// Make sure the type GooseDBVersion runs hooks after queries +var _ bob.HookableType = &GooseDBVersion{} + +// Make sure the type Organization runs hooks after queries +var _ bob.HookableType = &Organization{} + +// Make sure the type User runs hooks after queries +var _ bob.HookableType = &User{} + +// Make sure the type enums.ArcgisLicenseType satisfies database/sql.Scanner +var _ sql.Scanner = (*enums.ArcgisLicenseType)(nil) + +// Make sure the type enums.ArcgisLicenseType satisfies database/sql/driver.Valuer +var _ driver.Valuer = *new(enums.ArcgisLicenseType) diff --git a/models/bob_where.bob.go b/models/bob_where.bob.go new file mode 100644 index 00000000..13f1536c --- /dev/null +++ b/models/bob_where.bob.go @@ -0,0 +1,33 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "github.com/stephenafamo/bob/clause" + "github.com/stephenafamo/bob/dialect/psql" + "github.com/stephenafamo/bob/dialect/psql/dialect" +) + +var ( + SelectWhere = Where[*dialect.SelectQuery]() + UpdateWhere = Where[*dialect.UpdateQuery]() + DeleteWhere = Where[*dialect.DeleteQuery]() + OnConflictWhere = Where[*clause.ConflictClause]() // Used in ON CONFLICT DO UPDATE +) + +func Where[Q psql.Filterable]() struct { + GooseDBVersions gooseDBVersionWhere[Q] + Organizations organizationWhere[Q] + Users userWhere[Q] +} { + return struct { + GooseDBVersions gooseDBVersionWhere[Q] + Organizations organizationWhere[Q] + Users userWhere[Q] + }{ + GooseDBVersions: buildGooseDBVersionWhere[Q](GooseDBVersions.Columns), + Organizations: buildOrganizationWhere[Q](Organizations.Columns), + Users: buildUserWhere[Q](Users.Columns), + } +} diff --git a/models/goose_db_version.bob.go b/models/goose_db_version.bob.go new file mode 100644 index 00000000..40f2b3b9 --- /dev/null +++ b/models/goose_db_version.bob.go @@ -0,0 +1,424 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "io" + "time" + + "github.com/aarondl/opt/omit" + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/dialect/psql" + "github.com/stephenafamo/bob/dialect/psql/dialect" + "github.com/stephenafamo/bob/dialect/psql/dm" + "github.com/stephenafamo/bob/dialect/psql/sm" + "github.com/stephenafamo/bob/dialect/psql/um" + "github.com/stephenafamo/bob/expr" +) + +// GooseDBVersion is an object representing the database table. +type GooseDBVersion struct { + ID int32 `db:"id,pk" ` + VersionID int64 `db:"version_id" ` + IsApplied bool `db:"is_applied" ` + Tstamp time.Time `db:"tstamp" ` +} + +// GooseDBVersionSlice is an alias for a slice of pointers to GooseDBVersion. +// This should almost always be used instead of []*GooseDBVersion. +type GooseDBVersionSlice []*GooseDBVersion + +// GooseDBVersions contains methods to work with the goose_db_version table +var GooseDBVersions = psql.NewTablex[*GooseDBVersion, GooseDBVersionSlice, *GooseDBVersionSetter]("", "goose_db_version", buildGooseDBVersionColumns("goose_db_version")) + +// GooseDBVersionsQuery is a query on the goose_db_version table +type GooseDBVersionsQuery = *psql.ViewQuery[*GooseDBVersion, GooseDBVersionSlice] + +func buildGooseDBVersionColumns(alias string) gooseDBVersionColumns { + return gooseDBVersionColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "id", "version_id", "is_applied", "tstamp", + ).WithParent("goose_db_version"), + tableAlias: alias, + ID: psql.Quote(alias, "id"), + VersionID: psql.Quote(alias, "version_id"), + IsApplied: psql.Quote(alias, "is_applied"), + Tstamp: psql.Quote(alias, "tstamp"), + } +} + +type gooseDBVersionColumns struct { + expr.ColumnsExpr + tableAlias string + ID psql.Expression + VersionID psql.Expression + IsApplied psql.Expression + Tstamp psql.Expression +} + +func (c gooseDBVersionColumns) Alias() string { + return c.tableAlias +} + +func (gooseDBVersionColumns) AliasedAs(alias string) gooseDBVersionColumns { + return buildGooseDBVersionColumns(alias) +} + +// GooseDBVersionSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type GooseDBVersionSetter struct { + ID omit.Val[int32] `db:"id,pk" ` + VersionID omit.Val[int64] `db:"version_id" ` + IsApplied omit.Val[bool] `db:"is_applied" ` + Tstamp omit.Val[time.Time] `db:"tstamp" ` +} + +func (s GooseDBVersionSetter) SetColumns() []string { + vals := make([]string, 0, 4) + if s.ID.IsValue() { + vals = append(vals, "id") + } + if s.VersionID.IsValue() { + vals = append(vals, "version_id") + } + if s.IsApplied.IsValue() { + vals = append(vals, "is_applied") + } + if s.Tstamp.IsValue() { + vals = append(vals, "tstamp") + } + return vals +} + +func (s GooseDBVersionSetter) Overwrite(t *GooseDBVersion) { + if s.ID.IsValue() { + t.ID = s.ID.MustGet() + } + if s.VersionID.IsValue() { + t.VersionID = s.VersionID.MustGet() + } + if s.IsApplied.IsValue() { + t.IsApplied = s.IsApplied.MustGet() + } + if s.Tstamp.IsValue() { + t.Tstamp = s.Tstamp.MustGet() + } +} + +func (s *GooseDBVersionSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return GooseDBVersions.BeforeInsertHooks.RunHooks(ctx, exec, s) + }) + + q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + vals := make([]bob.Expression, 4) + if s.ID.IsValue() { + vals[0] = psql.Arg(s.ID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.VersionID.IsValue() { + vals[1] = psql.Arg(s.VersionID.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if s.IsApplied.IsValue() { + vals[2] = psql.Arg(s.IsApplied.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + if s.Tstamp.IsValue() { + vals[3] = psql.Arg(s.Tstamp.MustGet()) + } else { + vals[3] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s GooseDBVersionSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s GooseDBVersionSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 4) + + if s.ID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "id")...), + psql.Arg(s.ID), + }}) + } + + if s.VersionID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "version_id")...), + psql.Arg(s.VersionID), + }}) + } + + if s.IsApplied.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "is_applied")...), + psql.Arg(s.IsApplied), + }}) + } + + if s.Tstamp.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "tstamp")...), + psql.Arg(s.Tstamp), + }}) + } + + return exprs +} + +// FindGooseDBVersion retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindGooseDBVersion(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*GooseDBVersion, error) { + if len(cols) == 0 { + return GooseDBVersions.Query( + sm.Where(GooseDBVersions.Columns.ID.EQ(psql.Arg(IDPK))), + ).One(ctx, exec) + } + + return GooseDBVersions.Query( + sm.Where(GooseDBVersions.Columns.ID.EQ(psql.Arg(IDPK))), + sm.Columns(GooseDBVersions.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// GooseDBVersionExists checks the presence of a single record by primary key +func GooseDBVersionExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { + return GooseDBVersions.Query( + sm.Where(GooseDBVersions.Columns.ID.EQ(psql.Arg(IDPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after GooseDBVersion is retrieved from the database +func (o *GooseDBVersion) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = GooseDBVersions.AfterSelectHooks.RunHooks(ctx, exec, GooseDBVersionSlice{o}) + case bob.QueryTypeInsert: + ctx, err = GooseDBVersions.AfterInsertHooks.RunHooks(ctx, exec, GooseDBVersionSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = GooseDBVersions.AfterUpdateHooks.RunHooks(ctx, exec, GooseDBVersionSlice{o}) + case bob.QueryTypeDelete: + ctx, err = GooseDBVersions.AfterDeleteHooks.RunHooks(ctx, exec, GooseDBVersionSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the GooseDBVersion +func (o *GooseDBVersion) primaryKeyVals() bob.Expression { + return psql.Arg(o.ID) +} + +func (o *GooseDBVersion) pkEQ() dialect.Expression { + return psql.Quote("goose_db_version", "id").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + return o.primaryKeyVals().WriteSQL(ctx, w, d, start) + })) +} + +// Update uses an executor to update the GooseDBVersion +func (o *GooseDBVersion) Update(ctx context.Context, exec bob.Executor, s *GooseDBVersionSetter) error { + v, err := GooseDBVersions.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) + if err != nil { + return err + } + + *o = *v + + return nil +} + +// Delete deletes a single GooseDBVersion record with an executor +func (o *GooseDBVersion) Delete(ctx context.Context, exec bob.Executor) error { + _, err := GooseDBVersions.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the GooseDBVersion using the executor +func (o *GooseDBVersion) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := GooseDBVersions.Query( + sm.Where(GooseDBVersions.Columns.ID.EQ(psql.Arg(o.ID))), + ).One(ctx, exec) + if err != nil { + return err + } + + *o = *o2 + + return nil +} + +// AfterQueryHook is called after GooseDBVersionSlice is retrieved from the database +func (o GooseDBVersionSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = GooseDBVersions.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = GooseDBVersions.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = GooseDBVersions.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = GooseDBVersions.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o GooseDBVersionSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("goose_db_version", "id").In(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + pkPairs := make([]bob.Expression, len(o)) + for i, row := range o { + pkPairs[i] = row.primaryKeyVals() + } + return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "") + })) +} + +// copyMatchingRows finds models in the given slice that have the same primary key +// then it first copies the existing relationships from the old model to the new model +// and then replaces the old model in the slice with the new model +func (o GooseDBVersionSlice) copyMatchingRows(from ...*GooseDBVersion) { + for i, old := range o { + for _, new := range from { + if new.ID != old.ID { + continue + } + + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o GooseDBVersionSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return GooseDBVersions.BeforeUpdateHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *GooseDBVersion: + o.copyMatchingRows(retrieved) + case []*GooseDBVersion: + o.copyMatchingRows(retrieved...) + case GooseDBVersionSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a GooseDBVersion or a slice of GooseDBVersion + // then run the AfterUpdateHooks on the slice + _, err = GooseDBVersions.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o GooseDBVersionSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { + return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return GooseDBVersions.BeforeDeleteHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *GooseDBVersion: + o.copyMatchingRows(retrieved) + case []*GooseDBVersion: + o.copyMatchingRows(retrieved...) + case GooseDBVersionSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a GooseDBVersion or a slice of GooseDBVersion + // then run the AfterDeleteHooks on the slice + _, err = GooseDBVersions.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o GooseDBVersionSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals GooseDBVersionSetter) error { + if len(o) == 0 { + return nil + } + + _, err := GooseDBVersions.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o GooseDBVersionSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := GooseDBVersions.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o GooseDBVersionSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := GooseDBVersions.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +type gooseDBVersionWhere[Q psql.Filterable] struct { + ID psql.WhereMod[Q, int32] + VersionID psql.WhereMod[Q, int64] + IsApplied psql.WhereMod[Q, bool] + Tstamp psql.WhereMod[Q, time.Time] +} + +func (gooseDBVersionWhere[Q]) AliasedAs(alias string) gooseDBVersionWhere[Q] { + return buildGooseDBVersionWhere[Q](buildGooseDBVersionColumns(alias)) +} + +func buildGooseDBVersionWhere[Q psql.Filterable](cols gooseDBVersionColumns) gooseDBVersionWhere[Q] { + return gooseDBVersionWhere[Q]{ + ID: psql.Where[Q, int32](cols.ID), + VersionID: psql.Where[Q, int64](cols.VersionID), + IsApplied: psql.Where[Q, bool](cols.IsApplied), + Tstamp: psql.Where[Q, time.Time](cols.Tstamp), + } +} diff --git a/models/organization.bob.go b/models/organization.bob.go new file mode 100644 index 00000000..485fdff8 --- /dev/null +++ b/models/organization.bob.go @@ -0,0 +1,622 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "fmt" + "io" + + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/dialect/psql" + "github.com/stephenafamo/bob/dialect/psql/dialect" + "github.com/stephenafamo/bob/dialect/psql/dm" + "github.com/stephenafamo/bob/dialect/psql/sm" + "github.com/stephenafamo/bob/dialect/psql/um" + "github.com/stephenafamo/bob/expr" + "github.com/stephenafamo/bob/mods" + "github.com/stephenafamo/bob/orm" + "github.com/stephenafamo/bob/types/pgtypes" +) + +// Organization is an object representing the database table. +type Organization struct { + ID int32 `db:"id,pk" ` + Name null.Val[string] `db:"name" ` + + R organizationR `db:"-" ` +} + +// OrganizationSlice is an alias for a slice of pointers to Organization. +// This should almost always be used instead of []*Organization. +type OrganizationSlice []*Organization + +// Organizations contains methods to work with the organization table +var Organizations = psql.NewTablex[*Organization, OrganizationSlice, *OrganizationSetter]("", "organization", buildOrganizationColumns("organization")) + +// OrganizationsQuery is a query on the organization table +type OrganizationsQuery = *psql.ViewQuery[*Organization, OrganizationSlice] + +// organizationR is where relationships are stored. +type organizationR struct { + User UserSlice // user_.user__organization_id_fkey +} + +func buildOrganizationColumns(alias string) organizationColumns { + return organizationColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "id", "name", + ).WithParent("organization"), + tableAlias: alias, + ID: psql.Quote(alias, "id"), + Name: psql.Quote(alias, "name"), + } +} + +type organizationColumns struct { + expr.ColumnsExpr + tableAlias string + ID psql.Expression + Name psql.Expression +} + +func (c organizationColumns) Alias() string { + return c.tableAlias +} + +func (organizationColumns) AliasedAs(alias string) organizationColumns { + return buildOrganizationColumns(alias) +} + +// OrganizationSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type OrganizationSetter struct { + ID omit.Val[int32] `db:"id,pk" ` + Name omitnull.Val[string] `db:"name" ` +} + +func (s OrganizationSetter) SetColumns() []string { + vals := make([]string, 0, 2) + if s.ID.IsValue() { + vals = append(vals, "id") + } + if !s.Name.IsUnset() { + vals = append(vals, "name") + } + return vals +} + +func (s OrganizationSetter) Overwrite(t *Organization) { + if s.ID.IsValue() { + t.ID = s.ID.MustGet() + } + if !s.Name.IsUnset() { + t.Name = s.Name.MustGetNull() + } +} + +func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Organizations.BeforeInsertHooks.RunHooks(ctx, exec, s) + }) + + q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + vals := make([]bob.Expression, 2) + if s.ID.IsValue() { + vals[0] = psql.Arg(s.ID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if !s.Name.IsUnset() { + vals[1] = psql.Arg(s.Name.MustGetNull()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s OrganizationSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s OrganizationSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 2) + + if s.ID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "id")...), + psql.Arg(s.ID), + }}) + } + + if !s.Name.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "name")...), + psql.Arg(s.Name), + }}) + } + + return exprs +} + +// FindOrganization retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindOrganization(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*Organization, error) { + if len(cols) == 0 { + return Organizations.Query( + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(IDPK))), + ).One(ctx, exec) + } + + return Organizations.Query( + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(IDPK))), + sm.Columns(Organizations.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// OrganizationExists checks the presence of a single record by primary key +func OrganizationExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { + return Organizations.Query( + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(IDPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after Organization is retrieved from the database +func (o *Organization) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = Organizations.AfterSelectHooks.RunHooks(ctx, exec, OrganizationSlice{o}) + case bob.QueryTypeInsert: + ctx, err = Organizations.AfterInsertHooks.RunHooks(ctx, exec, OrganizationSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = Organizations.AfterUpdateHooks.RunHooks(ctx, exec, OrganizationSlice{o}) + case bob.QueryTypeDelete: + ctx, err = Organizations.AfterDeleteHooks.RunHooks(ctx, exec, OrganizationSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the Organization +func (o *Organization) primaryKeyVals() bob.Expression { + return psql.Arg(o.ID) +} + +func (o *Organization) pkEQ() dialect.Expression { + return psql.Quote("organization", "id").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + return o.primaryKeyVals().WriteSQL(ctx, w, d, start) + })) +} + +// Update uses an executor to update the Organization +func (o *Organization) Update(ctx context.Context, exec bob.Executor, s *OrganizationSetter) error { + v, err := Organizations.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) + if err != nil { + return err + } + + o.R = v.R + *o = *v + + return nil +} + +// Delete deletes a single Organization record with an executor +func (o *Organization) Delete(ctx context.Context, exec bob.Executor) error { + _, err := Organizations.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the Organization using the executor +func (o *Organization) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := Organizations.Query( + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(o.ID))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after OrganizationSlice is retrieved from the database +func (o OrganizationSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = Organizations.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = Organizations.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = Organizations.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = Organizations.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o OrganizationSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("organization", "id").In(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + pkPairs := make([]bob.Expression, len(o)) + for i, row := range o { + pkPairs[i] = row.primaryKeyVals() + } + return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "") + })) +} + +// copyMatchingRows finds models in the given slice that have the same primary key +// then it first copies the existing relationships from the old model to the new model +// and then replaces the old model in the slice with the new model +func (o OrganizationSlice) copyMatchingRows(from ...*Organization) { + for i, old := range o { + for _, new := range from { + if new.ID != old.ID { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o OrganizationSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Organizations.BeforeUpdateHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *Organization: + o.copyMatchingRows(retrieved) + case []*Organization: + o.copyMatchingRows(retrieved...) + case OrganizationSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a Organization or a slice of Organization + // then run the AfterUpdateHooks on the slice + _, err = Organizations.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o OrganizationSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { + return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Organizations.BeforeDeleteHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *Organization: + o.copyMatchingRows(retrieved) + case []*Organization: + o.copyMatchingRows(retrieved...) + case OrganizationSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a Organization or a slice of Organization + // then run the AfterDeleteHooks on the slice + _, err = Organizations.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o OrganizationSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals OrganizationSetter) error { + if len(o) == 0 { + return nil + } + + _, err := Organizations.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o OrganizationSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := Organizations.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o OrganizationSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := Organizations.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// User starts a query for related objects on user_ +func (o *Organization) User(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + return Users.Query(append(mods, + sm.Where(Users.Columns.OrganizationID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os OrganizationSlice) User(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return Users.Query(append(mods, + sm.Where(psql.Group(Users.Columns.OrganizationID).OP("IN", PKArgExpr)), + )...) +} + +func insertOrganizationUser0(ctx context.Context, exec bob.Executor, users1 []*UserSetter, organization0 *Organization) (UserSlice, error) { + for i := range users1 { + users1[i].OrganizationID = omitnull.From(organization0.ID) + } + + ret, err := Users.Insert(bob.ToMods(users1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertOrganizationUser0: %w", err) + } + + return ret, nil +} + +func attachOrganizationUser0(ctx context.Context, exec bob.Executor, count int, users1 UserSlice, organization0 *Organization) (UserSlice, error) { + setter := &UserSetter{ + OrganizationID: omitnull.From(organization0.ID), + } + + err := users1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachOrganizationUser0: %w", err) + } + + return users1, nil +} + +func (organization0 *Organization) InsertUser(ctx context.Context, exec bob.Executor, related ...*UserSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + users1, err := insertOrganizationUser0(ctx, exec, related, organization0) + if err != nil { + return err + } + + organization0.R.User = append(organization0.R.User, users1...) + + for _, rel := range users1 { + rel.R.Organization = organization0 + } + return nil +} + +func (organization0 *Organization) AttachUser(ctx context.Context, exec bob.Executor, related ...*User) error { + if len(related) == 0 { + return nil + } + + var err error + users1 := UserSlice(related) + + _, err = attachOrganizationUser0(ctx, exec, len(related), users1, organization0) + if err != nil { + return err + } + + organization0.R.User = append(organization0.R.User, users1...) + + for _, rel := range related { + rel.R.Organization = organization0 + } + + return nil +} + +type organizationWhere[Q psql.Filterable] struct { + ID psql.WhereMod[Q, int32] + Name psql.WhereNullMod[Q, string] +} + +func (organizationWhere[Q]) AliasedAs(alias string) organizationWhere[Q] { + return buildOrganizationWhere[Q](buildOrganizationColumns(alias)) +} + +func buildOrganizationWhere[Q psql.Filterable](cols organizationColumns) organizationWhere[Q] { + return organizationWhere[Q]{ + ID: psql.Where[Q, int32](cols.ID), + Name: psql.WhereNull[Q, string](cols.Name), + } +} + +func (o *Organization) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "User": + rels, ok := retrieved.(UserSlice) + if !ok { + return fmt.Errorf("organization cannot load %T as %q", retrieved, name) + } + + o.R.User = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Organization = o + } + } + return nil + default: + return fmt.Errorf("organization has no relationship %q", name) + } +} + +type organizationPreloader struct{} + +func buildOrganizationPreloader() organizationPreloader { + return organizationPreloader{} +} + +type organizationThenLoader[Q orm.Loadable] struct { + User func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { + type UserLoadInterface interface { + LoadUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return organizationThenLoader[Q]{ + User: thenLoadBuilder[Q]( + "User", + func(ctx context.Context, exec bob.Executor, retrieved UserLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadUser(ctx, exec, mods...) + }, + ), + } +} + +// LoadUser loads the organization's User into the .R struct +func (o *Organization) LoadUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.User = nil + + related, err := o.User(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Organization = o + } + + o.R.User = related + return nil +} + +// LoadUser loads the organization's User into the .R struct +func (os OrganizationSlice) LoadUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + users, err := os.User(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.User = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range users { + + if !rel.OrganizationID.IsValue() { + continue + } + if !(rel.OrganizationID.IsValue() && o.ID == rel.OrganizationID.MustGet()) { + continue + } + + rel.R.Organization = o + + o.R.User = append(o.R.User, rel) + } + } + + return nil +} + +type organizationJoins[Q dialect.Joinable] struct { + typ string + User modAs[Q, userColumns] +} + +func (j organizationJoins[Q]) aliasedAs(alias string) organizationJoins[Q] { + return buildOrganizationJoins[Q](buildOrganizationColumns(alias), j.typ) +} + +func buildOrganizationJoins[Q dialect.Joinable](cols organizationColumns, typ string) organizationJoins[Q] { + return organizationJoins[Q]{ + typ: typ, + User: modAs[Q, userColumns]{ + c: Users.Columns, + f: func(to userColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Users.Name().As(to.Alias())).On( + to.OrganizationID.EQ(cols.ID), + )) + } + + return mods + }, + }, + } +} diff --git a/models/user_.bob.go b/models/user_.bob.go new file mode 100644 index 00000000..885ac320 --- /dev/null +++ b/models/user_.bob.go @@ -0,0 +1,809 @@ +// Code generated by BobGen psql v0.41.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "fmt" + "io" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/enums" + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/dialect/psql" + "github.com/stephenafamo/bob/dialect/psql/dialect" + "github.com/stephenafamo/bob/dialect/psql/dm" + "github.com/stephenafamo/bob/dialect/psql/sm" + "github.com/stephenafamo/bob/dialect/psql/um" + "github.com/stephenafamo/bob/expr" + "github.com/stephenafamo/bob/mods" + "github.com/stephenafamo/bob/orm" + "github.com/stephenafamo/bob/types/pgtypes" +) + +// User is an object representing the database table. +type User struct { + ID int32 `db:"id,pk" ` + ArcgisAccessToken null.Val[string] `db:"arcgis_access_token" ` + ArcgisLicense null.Val[enums.ArcgisLicenseType] `db:"arcgis_license" ` + ArcgisRefreshToken null.Val[string] `db:"arcgis_refresh_token" ` + ArcgisRefreshTokenExpires null.Val[time.Time] `db:"arcgis_refresh_token_expires" ` + ArcgisRole null.Val[string] `db:"arcgis_role" ` + DisplayName null.Val[string] `db:"display_name" ` + Email null.Val[string] `db:"email" ` + OrganizationID null.Val[int32] `db:"organization_id" ` + Username string `db:"username" ` + + R userR `db:"-" ` +} + +// UserSlice is an alias for a slice of pointers to User. +// This should almost always be used instead of []*User. +type UserSlice []*User + +// Users contains methods to work with the user_ table +var Users = psql.NewTablex[*User, UserSlice, *UserSetter]("", "user_", buildUserColumns("user_")) + +// UsersQuery is a query on the user_ table +type UsersQuery = *psql.ViewQuery[*User, UserSlice] + +// userR is where relationships are stored. +type userR struct { + Organization *Organization // user_.user__organization_id_fkey +} + +func buildUserColumns(alias string) userColumns { + return userColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "id", "arcgis_access_token", "arcgis_license", "arcgis_refresh_token", "arcgis_refresh_token_expires", "arcgis_role", "display_name", "email", "organization_id", "username", + ).WithParent("user_"), + tableAlias: alias, + ID: psql.Quote(alias, "id"), + ArcgisAccessToken: psql.Quote(alias, "arcgis_access_token"), + ArcgisLicense: psql.Quote(alias, "arcgis_license"), + ArcgisRefreshToken: psql.Quote(alias, "arcgis_refresh_token"), + ArcgisRefreshTokenExpires: psql.Quote(alias, "arcgis_refresh_token_expires"), + ArcgisRole: psql.Quote(alias, "arcgis_role"), + DisplayName: psql.Quote(alias, "display_name"), + Email: psql.Quote(alias, "email"), + OrganizationID: psql.Quote(alias, "organization_id"), + Username: psql.Quote(alias, "username"), + } +} + +type userColumns struct { + expr.ColumnsExpr + tableAlias string + ID psql.Expression + ArcgisAccessToken psql.Expression + ArcgisLicense psql.Expression + ArcgisRefreshToken psql.Expression + ArcgisRefreshTokenExpires psql.Expression + ArcgisRole psql.Expression + DisplayName psql.Expression + Email psql.Expression + OrganizationID psql.Expression + Username psql.Expression +} + +func (c userColumns) Alias() string { + return c.tableAlias +} + +func (userColumns) AliasedAs(alias string) userColumns { + return buildUserColumns(alias) +} + +// UserSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type UserSetter struct { + ID omit.Val[int32] `db:"id,pk" ` + ArcgisAccessToken omitnull.Val[string] `db:"arcgis_access_token" ` + ArcgisLicense omitnull.Val[enums.ArcgisLicenseType] `db:"arcgis_license" ` + ArcgisRefreshToken omitnull.Val[string] `db:"arcgis_refresh_token" ` + ArcgisRefreshTokenExpires omitnull.Val[time.Time] `db:"arcgis_refresh_token_expires" ` + ArcgisRole omitnull.Val[string] `db:"arcgis_role" ` + DisplayName omitnull.Val[string] `db:"display_name" ` + Email omitnull.Val[string] `db:"email" ` + OrganizationID omitnull.Val[int32] `db:"organization_id" ` + Username omit.Val[string] `db:"username" ` +} + +func (s UserSetter) SetColumns() []string { + vals := make([]string, 0, 10) + if s.ID.IsValue() { + vals = append(vals, "id") + } + if !s.ArcgisAccessToken.IsUnset() { + vals = append(vals, "arcgis_access_token") + } + if !s.ArcgisLicense.IsUnset() { + vals = append(vals, "arcgis_license") + } + if !s.ArcgisRefreshToken.IsUnset() { + vals = append(vals, "arcgis_refresh_token") + } + if !s.ArcgisRefreshTokenExpires.IsUnset() { + vals = append(vals, "arcgis_refresh_token_expires") + } + if !s.ArcgisRole.IsUnset() { + vals = append(vals, "arcgis_role") + } + if !s.DisplayName.IsUnset() { + vals = append(vals, "display_name") + } + if !s.Email.IsUnset() { + vals = append(vals, "email") + } + if !s.OrganizationID.IsUnset() { + vals = append(vals, "organization_id") + } + if s.Username.IsValue() { + vals = append(vals, "username") + } + return vals +} + +func (s UserSetter) Overwrite(t *User) { + if s.ID.IsValue() { + t.ID = s.ID.MustGet() + } + if !s.ArcgisAccessToken.IsUnset() { + t.ArcgisAccessToken = s.ArcgisAccessToken.MustGetNull() + } + if !s.ArcgisLicense.IsUnset() { + t.ArcgisLicense = s.ArcgisLicense.MustGetNull() + } + if !s.ArcgisRefreshToken.IsUnset() { + t.ArcgisRefreshToken = s.ArcgisRefreshToken.MustGetNull() + } + if !s.ArcgisRefreshTokenExpires.IsUnset() { + t.ArcgisRefreshTokenExpires = s.ArcgisRefreshTokenExpires.MustGetNull() + } + if !s.ArcgisRole.IsUnset() { + t.ArcgisRole = s.ArcgisRole.MustGetNull() + } + if !s.DisplayName.IsUnset() { + t.DisplayName = s.DisplayName.MustGetNull() + } + if !s.Email.IsUnset() { + t.Email = s.Email.MustGetNull() + } + if !s.OrganizationID.IsUnset() { + t.OrganizationID = s.OrganizationID.MustGetNull() + } + if s.Username.IsValue() { + t.Username = s.Username.MustGet() + } +} + +func (s *UserSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Users.BeforeInsertHooks.RunHooks(ctx, exec, s) + }) + + q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + vals := make([]bob.Expression, 10) + if s.ID.IsValue() { + vals[0] = psql.Arg(s.ID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if !s.ArcgisAccessToken.IsUnset() { + vals[1] = psql.Arg(s.ArcgisAccessToken.MustGetNull()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if !s.ArcgisLicense.IsUnset() { + vals[2] = psql.Arg(s.ArcgisLicense.MustGetNull()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + if !s.ArcgisRefreshToken.IsUnset() { + vals[3] = psql.Arg(s.ArcgisRefreshToken.MustGetNull()) + } else { + vals[3] = psql.Raw("DEFAULT") + } + + if !s.ArcgisRefreshTokenExpires.IsUnset() { + vals[4] = psql.Arg(s.ArcgisRefreshTokenExpires.MustGetNull()) + } else { + vals[4] = psql.Raw("DEFAULT") + } + + if !s.ArcgisRole.IsUnset() { + vals[5] = psql.Arg(s.ArcgisRole.MustGetNull()) + } else { + vals[5] = psql.Raw("DEFAULT") + } + + if !s.DisplayName.IsUnset() { + vals[6] = psql.Arg(s.DisplayName.MustGetNull()) + } else { + vals[6] = psql.Raw("DEFAULT") + } + + if !s.Email.IsUnset() { + vals[7] = psql.Arg(s.Email.MustGetNull()) + } else { + vals[7] = psql.Raw("DEFAULT") + } + + if !s.OrganizationID.IsUnset() { + vals[8] = psql.Arg(s.OrganizationID.MustGetNull()) + } else { + vals[8] = psql.Raw("DEFAULT") + } + + if s.Username.IsValue() { + vals[9] = psql.Arg(s.Username.MustGet()) + } else { + vals[9] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s UserSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s UserSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 10) + + if s.ID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "id")...), + psql.Arg(s.ID), + }}) + } + + if !s.ArcgisAccessToken.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "arcgis_access_token")...), + psql.Arg(s.ArcgisAccessToken), + }}) + } + + if !s.ArcgisLicense.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "arcgis_license")...), + psql.Arg(s.ArcgisLicense), + }}) + } + + if !s.ArcgisRefreshToken.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "arcgis_refresh_token")...), + psql.Arg(s.ArcgisRefreshToken), + }}) + } + + if !s.ArcgisRefreshTokenExpires.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "arcgis_refresh_token_expires")...), + psql.Arg(s.ArcgisRefreshTokenExpires), + }}) + } + + if !s.ArcgisRole.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "arcgis_role")...), + psql.Arg(s.ArcgisRole), + }}) + } + + if !s.DisplayName.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "display_name")...), + psql.Arg(s.DisplayName), + }}) + } + + if !s.Email.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "email")...), + psql.Arg(s.Email), + }}) + } + + if !s.OrganizationID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "organization_id")...), + psql.Arg(s.OrganizationID), + }}) + } + + if s.Username.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "username")...), + psql.Arg(s.Username), + }}) + } + + return exprs +} + +// FindUser retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindUser(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*User, error) { + if len(cols) == 0 { + return Users.Query( + sm.Where(Users.Columns.ID.EQ(psql.Arg(IDPK))), + ).One(ctx, exec) + } + + return Users.Query( + sm.Where(Users.Columns.ID.EQ(psql.Arg(IDPK))), + sm.Columns(Users.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// UserExists checks the presence of a single record by primary key +func UserExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { + return Users.Query( + sm.Where(Users.Columns.ID.EQ(psql.Arg(IDPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after User is retrieved from the database +func (o *User) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = Users.AfterSelectHooks.RunHooks(ctx, exec, UserSlice{o}) + case bob.QueryTypeInsert: + ctx, err = Users.AfterInsertHooks.RunHooks(ctx, exec, UserSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = Users.AfterUpdateHooks.RunHooks(ctx, exec, UserSlice{o}) + case bob.QueryTypeDelete: + ctx, err = Users.AfterDeleteHooks.RunHooks(ctx, exec, UserSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the User +func (o *User) primaryKeyVals() bob.Expression { + return psql.Arg(o.ID) +} + +func (o *User) pkEQ() dialect.Expression { + return psql.Quote("user_", "id").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + return o.primaryKeyVals().WriteSQL(ctx, w, d, start) + })) +} + +// Update uses an executor to update the User +func (o *User) Update(ctx context.Context, exec bob.Executor, s *UserSetter) error { + v, err := Users.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) + if err != nil { + return err + } + + o.R = v.R + *o = *v + + return nil +} + +// Delete deletes a single User record with an executor +func (o *User) Delete(ctx context.Context, exec bob.Executor) error { + _, err := Users.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the User using the executor +func (o *User) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := Users.Query( + sm.Where(Users.Columns.ID.EQ(psql.Arg(o.ID))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after UserSlice is retrieved from the database +func (o UserSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = Users.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = Users.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = Users.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = Users.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o UserSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("user_", "id").In(bob.ExpressionFunc(func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) { + pkPairs := make([]bob.Expression, len(o)) + for i, row := range o { + pkPairs[i] = row.primaryKeyVals() + } + return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "") + })) +} + +// copyMatchingRows finds models in the given slice that have the same primary key +// then it first copies the existing relationships from the old model to the new model +// and then replaces the old model in the slice with the new model +func (o UserSlice) copyMatchingRows(from ...*User) { + for i, old := range o { + for _, new := range from { + if new.ID != old.ID { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o UserSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Users.BeforeUpdateHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *User: + o.copyMatchingRows(retrieved) + case []*User: + o.copyMatchingRows(retrieved...) + case UserSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a User or a slice of User + // then run the AfterUpdateHooks on the slice + _, err = Users.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o UserSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { + return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return Users.BeforeDeleteHooks.RunHooks(ctx, exec, o) + }) + + q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error { + var err error + switch retrieved := retrieved.(type) { + case *User: + o.copyMatchingRows(retrieved) + case []*User: + o.copyMatchingRows(retrieved...) + case UserSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a User or a slice of User + // then run the AfterDeleteHooks on the slice + _, err = Users.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o UserSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals UserSetter) error { + if len(o) == 0 { + return nil + } + + _, err := Users.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o UserSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := Users.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o UserSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := Users.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// Organization starts a query for related objects on organization +func (o *User) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(o.OrganizationID))), + )...) +} + +func (os UserSlice) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + pkOrganizationID := make(pgtypes.Array[null.Val[int32]], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkOrganizationID = append(pkOrganizationID, o.OrganizationID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkOrganizationID), "integer[]")), + )) + + return Organizations.Query(append(mods, + sm.Where(psql.Group(Organizations.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +func attachUserOrganization0(ctx context.Context, exec bob.Executor, count int, user0 *User, organization1 *Organization) (*User, error) { + setter := &UserSetter{ + OrganizationID: omitnull.From(organization1.ID), + } + + err := user0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachUserOrganization0: %w", err) + } + + return user0, nil +} + +func (user0 *User) InsertOrganization(ctx context.Context, exec bob.Executor, related *OrganizationSetter) error { + var err error + + organization1, err := Organizations.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachUserOrganization0(ctx, exec, 1, user0, organization1) + if err != nil { + return err + } + + user0.R.Organization = organization1 + + organization1.R.User = append(organization1.R.User, user0) + + return nil +} + +func (user0 *User) AttachOrganization(ctx context.Context, exec bob.Executor, organization1 *Organization) error { + var err error + + _, err = attachUserOrganization0(ctx, exec, 1, user0, organization1) + if err != nil { + return err + } + + user0.R.Organization = organization1 + + organization1.R.User = append(organization1.R.User, user0) + + return nil +} + +type userWhere[Q psql.Filterable] struct { + ID psql.WhereMod[Q, int32] + ArcgisAccessToken psql.WhereNullMod[Q, string] + ArcgisLicense psql.WhereNullMod[Q, enums.ArcgisLicenseType] + ArcgisRefreshToken psql.WhereNullMod[Q, string] + ArcgisRefreshTokenExpires psql.WhereNullMod[Q, time.Time] + ArcgisRole psql.WhereNullMod[Q, string] + DisplayName psql.WhereNullMod[Q, string] + Email psql.WhereNullMod[Q, string] + OrganizationID psql.WhereNullMod[Q, int32] + Username psql.WhereMod[Q, string] +} + +func (userWhere[Q]) AliasedAs(alias string) userWhere[Q] { + return buildUserWhere[Q](buildUserColumns(alias)) +} + +func buildUserWhere[Q psql.Filterable](cols userColumns) userWhere[Q] { + return userWhere[Q]{ + ID: psql.Where[Q, int32](cols.ID), + ArcgisAccessToken: psql.WhereNull[Q, string](cols.ArcgisAccessToken), + ArcgisLicense: psql.WhereNull[Q, enums.ArcgisLicenseType](cols.ArcgisLicense), + ArcgisRefreshToken: psql.WhereNull[Q, string](cols.ArcgisRefreshToken), + ArcgisRefreshTokenExpires: psql.WhereNull[Q, time.Time](cols.ArcgisRefreshTokenExpires), + ArcgisRole: psql.WhereNull[Q, string](cols.ArcgisRole), + DisplayName: psql.WhereNull[Q, string](cols.DisplayName), + Email: psql.WhereNull[Q, string](cols.Email), + OrganizationID: psql.WhereNull[Q, int32](cols.OrganizationID), + Username: psql.Where[Q, string](cols.Username), + } +} + +func (o *User) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "Organization": + rel, ok := retrieved.(*Organization) + if !ok { + return fmt.Errorf("user cannot load %T as %q", retrieved, name) + } + + o.R.Organization = rel + + if rel != nil { + rel.R.User = UserSlice{o} + } + return nil + default: + return fmt.Errorf("user has no relationship %q", name) + } +} + +type userPreloader struct { + Organization func(...psql.PreloadOption) psql.Preloader +} + +func buildUserPreloader() userPreloader { + return userPreloader{ + Organization: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*Organization, OrganizationSlice](psql.PreloadRel{ + Name: "Organization", + Sides: []psql.PreloadSide{ + { + From: Users, + To: Organizations, + FromColumns: []string{"organization_id"}, + ToColumns: []string{"id"}, + }, + }, + }, Organizations.Columns.Names(), opts...) + }, + } +} + +type userThenLoader[Q orm.Loadable] struct { + Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildUserThenLoader[Q orm.Loadable]() userThenLoader[Q] { + type OrganizationLoadInterface interface { + LoadOrganization(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return userThenLoader[Q]{ + Organization: thenLoadBuilder[Q]( + "Organization", + func(ctx context.Context, exec bob.Executor, retrieved OrganizationLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadOrganization(ctx, exec, mods...) + }, + ), + } +} + +// LoadOrganization loads the user's Organization into the .R struct +func (o *User) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Organization = nil + + related, err := o.Organization(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.User = UserSlice{o} + + o.R.Organization = related + return nil +} + +// LoadOrganization loads the user's Organization into the .R struct +func (os UserSlice) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + organizations, err := os.Organization(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range organizations { + if !o.OrganizationID.IsValue() { + continue + } + + if !(o.OrganizationID.IsValue() && o.OrganizationID.MustGet() == rel.ID) { + continue + } + + rel.R.User = append(rel.R.User, o) + + o.R.Organization = rel + break + } + } + + return nil +} + +type userJoins[Q dialect.Joinable] struct { + typ string + Organization modAs[Q, organizationColumns] +} + +func (j userJoins[Q]) aliasedAs(alias string) userJoins[Q] { + return buildUserJoins[Q](buildUserColumns(alias), j.typ) +} + +func buildUserJoins[Q dialect.Joinable](cols userColumns, typ string) userJoins[Q] { + return userJoins[Q]{ + typ: typ, + Organization: modAs[Q, organizationColumns]{ + c: Organizations.Columns, + f: func(to organizationColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Organizations.Name().As(to.Alias())).On( + to.ID.EQ(cols.OrganizationID), + )) + } + + return mods + }, + }, + } +}