diff --git a/backend/Taskfile.yml b/backend/Taskfile.yml index 1e06cce..4de171e 100644 --- a/backend/Taskfile.yml +++ b/backend/Taskfile.yml @@ -30,7 +30,7 @@ tasks: silent: true - cmd: rm -f dist/bin/* silent: true - - cmd: go build -buildvcs=false -ldflags="-X main.commit={{.GIT_COMMIT}} -X main.version={{.VERSION}}" -o ../dist/bin/server ./cmd/server/main.go + - cmd: go build -tags 'json1' -buildvcs=false -ldflags="-X main.commit={{.GIT_COMMIT}} -X main.version={{.VERSION}}" -o ../dist/bin/server ./cmd/server/main.go silent: true - cmd: go build -buildvcs=false -ldflags="-X main.commit={{.GIT_COMMIT}} -X main.version={{.VERSION}}" -o ../dist/bin/ipranges ./cmd/ipranges/main.go silent: true diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index f1e5ad3..30e5628 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -11,6 +11,8 @@ import ( "npm/internal/entity/certificate" "npm/internal/entity/host" "npm/internal/entity/setting" + "npm/internal/entity/user" + "npm/internal/errors" "npm/internal/jobqueue" "npm/internal/logger" ) @@ -25,7 +27,7 @@ func main() { database.Migrate(func() { setting.ApplySettings() - database.CheckSetup() + checkSetup() // Internal Job Queue jobqueue.Start() @@ -41,7 +43,8 @@ func main() { if irq == syscall.SIGINT || irq == syscall.SIGTERM { logger.Info("Got ", irq, " shutting server down ...") // Close db - err := database.GetInstance().Close() + sqlDB, _ := database.GetDB().DB() + err := sqlDB.Close() if err != nil { logger.Error("DatabaseCloseError", err) } @@ -52,3 +55,28 @@ func main() { } }) } + +// checkSetup Quick check by counting the number of users in the database +func checkSetup() { + db := database.GetDB() + var count int64 + + if db != nil { + db.Model(&user.Model{}). + Where("is_disabled = ?", false). + Where("is_system = ?", false). + Count(&count) + + if count == 0 { + logger.Warn("No users found, starting in Setup Mode") + } else { + config.IsSetup = true + logger.Info("Application is setup") + } + if config.ErrorReporting { + logger.Warn("Error reporting is enabled - Application Errors WILL be sent to Sentry, you can disable this in the Settings interface") + } + } else { + logger.Error("DatabaseError", errors.ErrDatabaseUnavailable) + } +} diff --git a/backend/db/schema.sql b/backend/db/schema.sql new file mode 100644 index 0000000..578c2e6 --- /dev/null +++ b/backend/db/schema.sql @@ -0,0 +1,3 @@ +INSERT INTO "schema_migrations" (version) VALUES + ('20201013035318'), + ('20201013035839'); diff --git a/backend/embed/main.go b/backend/embed/main.go index 2d3f32c..4323d28 100644 --- a/backend/embed/main.go +++ b/backend/embed/main.go @@ -14,7 +14,7 @@ var Assets embed.FS // MigrationFiles are database migrations // -//go:embed migrations/*.sql +//go:embed migrations var MigrationFiles embed.FS // NginxFiles hold nginx config templates diff --git a/backend/embed/migrations/20201013035318_initial_schema.sql b/backend/embed/migrations/sqlite/20201013035318_initial_schema.sql similarity index 79% rename from backend/embed/migrations/20201013035318_initial_schema.sql rename to backend/embed/migrations/sqlite/20201013035318_initial_schema.sql index 490769b..ac4bfe8 100644 --- a/backend/embed/migrations/20201013035318_initial_schema.sql +++ b/backend/embed/migrations/sqlite/20201013035318_initial_schema.sql @@ -3,40 +3,39 @@ CREATE TABLE IF NOT EXISTS `user` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, name TEXT NOT NULL, nickname TEXT NOT NULL, email TEXT NOT NULL, is_system INTEGER NOT NULL DEFAULT 0, - is_disabled INTEGER NOT NULL DEFAULT 0, - is_deleted INTEGER NOT NULL DEFAULT 0 + is_disabled INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS `capability` ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, + name TEXT PRIMARY KEY, UNIQUE (name) ); CREATE TABLE IF NOT EXISTS `user_has_capability` ( user_id INTEGER NOT NULL, - capability_id INTEGER NOT NULL, - UNIQUE (user_id, capability_id), - FOREIGN KEY (capability_id) REFERENCES capability (id) + capability_name TEXT NOT NULL, + UNIQUE (user_id, capability_name), + FOREIGN KEY (capability_name) REFERENCES capability (name) ); CREATE TABLE IF NOT EXISTS `auth` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL, type TEXT NOT NULL, secret TEXT NOT NULL, - is_deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user (id), UNIQUE (user_id, type) ); @@ -44,8 +43,9 @@ CREATE TABLE IF NOT EXISTS `auth` CREATE TABLE IF NOT EXISTS `setting` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, name TEXT NOT NULL, description TEXT NOT NULL DEFAULT "", value TEXT NOT NULL, @@ -55,8 +55,9 @@ CREATE TABLE IF NOT EXISTS `setting` CREATE TABLE IF NOT EXISTS `audit_log` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL, object_type TEXT NOT NULL, object_id INTEGER NOT NULL, @@ -68,36 +69,37 @@ CREATE TABLE IF NOT EXISTS `audit_log` CREATE TABLE IF NOT EXISTS `certificate_authority` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, name TEXT NOT NULL, acmesh_server TEXT NOT NULL DEFAULT "", ca_bundle TEXT NOT NULL DEFAULT "", is_wildcard_supported INTEGER NOT NULL DEFAULT 0, -- specific to each CA, acme v1 doesn't usually have wildcards max_domains INTEGER NOT NULL DEFAULT 5, -- per request - is_readonly INTEGER NOT NULL DEFAULT 0, - is_deleted INTEGER NOT NULL DEFAULT 0 + is_readonly INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS `dns_provider` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL, name TEXT NOT NULL, acmesh_name TEXT NOT NULL, dns_sleep INTEGER NOT NULL DEFAULT 0, meta TEXT NOT NULL, - is_deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user (id) ); CREATE TABLE IF NOT EXISTS `certificate` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, type TEXT NOT NULL, -- custom,dns,http user_id INTEGER NOT NULL, certificate_authority_id INTEGER, -- 0 for a custom cert @@ -109,7 +111,6 @@ CREATE TABLE IF NOT EXISTS `certificate` error_message text NOT NULL DEFAULT "", meta TEXT NOT NULL, is_ecc INTEGER NOT NULL DEFAULT 0, - is_deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (certificate_authority_id) REFERENCES certificate_authority (id), FOREIGN KEY (dns_provider_id) REFERENCES dns_provider (id) @@ -118,8 +119,9 @@ CREATE TABLE IF NOT EXISTS `certificate` CREATE TABLE IF NOT EXISTS `stream` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL, listen_interface TEXT NOT NULL, incoming_port INTEGER NOT NULL, @@ -127,15 +129,15 @@ CREATE TABLE IF NOT EXISTS `stream` udp_forwarding INTEGER NOT NULL DEFAULT 0, advanced_config TEXT NOT NULL, is_disabled INTEGER NOT NULL DEFAULT 0, - is_deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user (id) ); CREATE TABLE IF NOT EXISTS `upstream` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL, name TEXT NOT NULL, nginx_template_id INTEGER NOT NULL, @@ -148,7 +150,6 @@ CREATE TABLE IF NOT EXISTS `upstream` advanced_config TEXT NOT NULL, status TEXT NOT NULL DEFAULT "", error_message TEXT NOT NULL DEFAULT "", - is_deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (nginx_template_id) REFERENCES nginx_template (id) ); @@ -156,49 +157,50 @@ CREATE TABLE IF NOT EXISTS `upstream` CREATE TABLE IF NOT EXISTS `upstream_server` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, upstream_id INTEGER NOT NULL, server TEXT NOT NULL, weight INTEGER NOT NULL DEFAULT 0, max_conns INTEGER NOT NULL DEFAULT 0, max_fails INTEGER NOT NULL DEFAULT 0, fail_timeout INTEGER NOT NULL DEFAULT 0, - backup INTEGER NOT NULL DEFAULT 0, - is_deleted INTEGER NOT NULL DEFAULT 0, + is_backup INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (upstream_id) REFERENCES upstream (id) ); CREATE TABLE IF NOT EXISTS `access_list` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL, name TEXT NOT NULL, meta TEXT NOT NULL, - is_deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user (id) ); CREATE TABLE IF NOT EXISTS `nginx_template` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL, name TEXT NOT NULL, type TEXT NOT NULL, template TEXT NOT NULL, - is_deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user (id) ); CREATE TABLE IF NOT EXISTS `host` ( id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on INTEGER NOT NULL DEFAULT 0, - modified_on INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL DEFAULT 0, + is_deleted INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL, type TEXT NOT NULL, nginx_template_id INTEGER NOT NULL, @@ -222,7 +224,6 @@ CREATE TABLE IF NOT EXISTS `host` status TEXT NOT NULL DEFAULT "", error_message TEXT NOT NULL DEFAULT "", is_disabled INTEGER NOT NULL DEFAULT 0, - is_deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (nginx_template_id) REFERENCES nginx_template (id), FOREIGN KEY (upstream_id) REFERENCES upstream (id), diff --git a/backend/embed/migrations/20201013035839_initial_data.sql b/backend/embed/migrations/sqlite/20201013035839_initial_data.sql similarity index 89% rename from backend/embed/migrations/20201013035839_initial_data.sql rename to backend/embed/migrations/sqlite/20201013035839_initial_data.sql index d4e81fa..4d8b801 100644 --- a/backend/embed/migrations/20201013035839_initial_data.sql +++ b/backend/embed/migrations/sqlite/20201013035839_initial_data.sql @@ -25,14 +25,14 @@ INSERT INTO `capability` ( -- Default error reporting setting INSERT INTO `setting` ( - created_on, - modified_on, + created_at, + updated_at, name, description, value ) VALUES ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, "error-reporting", "If enabled, any application errors are reported to Sentry. Sensitive information is not sent.", "true" -- remember this is json @@ -40,14 +40,14 @@ INSERT INTO `setting` ( -- Default site INSERT INTO `setting` ( - created_on, - modified_on, + created_at, + updated_at, name, description, value ) VALUES ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, "default-site", "What to show users who hit your Nginx server by default", '"welcome"' -- remember this is json @@ -56,56 +56,56 @@ INSERT INTO `setting` ( -- Default Certificate Authorities INSERT INTO `certificate_authority` ( - created_on, - modified_on, + created_at, + updated_at, name, acmesh_server, is_wildcard_supported, max_domains, is_readonly ) VALUES ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, "ZeroSSL", "zerossl", 1, 10, 1 ), ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, "Let's Encrypt", "https://acme-v02.api.letsencrypt.org/directory", 1, 10, 1 ), ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, "Buypass Go SSL", "https://api.buypass.com/acme/directory", 0, 5, 1 ), ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, "SSL.com", "ssl.com", 0, 10, 1 ), ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, "Let's Encrypt (Testing)", "https://acme-staging-v02.api.letsencrypt.org/directory", 1, 10, 1 ), ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, "Buypass Go SSL (Testing)", "https://api.test4.buypass.no/acme/directory", 0, @@ -115,15 +115,15 @@ INSERT INTO `certificate_authority` ( -- System User INSERT INTO `user` ( - created_on, - modified_on, + created_at, + updated_at, name, nickname, email, is_system ) VALUES ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, "System", "System", "system@localhost", @@ -132,15 +132,15 @@ INSERT INTO `user` ( -- Host Templates INSERT INTO `nginx_template` ( - created_on, - modified_on, + created_at, + updated_at, user_id, name, type, template ) VALUES ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, (SELECT id FROM user WHERE is_system = 1 LIMIT 1), "Default Proxy Template", "proxy", @@ -262,29 +262,29 @@ server { } " ), ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, (SELECT id FROM user WHERE is_system = 1 LIMIT 1), "Default Redirect Template", "redirect", "# this is a redirect template" ), ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, (SELECT id FROM user WHERE is_system = 1 LIMIT 1), "Default Dead Template", "dead", "# this is a dead template" ), ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, (SELECT id FROM user WHERE is_system = 1 LIMIT 1), "Default Stream Template", "stream", "# this is a stream template" ), ( - strftime('%s', 'now'), - strftime('%s', 'now'), + unixepoch() * 1000, + unixepoch() * 1000, (SELECT id FROM user WHERE is_system = 1 LIMIT 1), "Default Upstream Template", "upstream", diff --git a/backend/go.mod b/backend/go.mod index 5d94cbf..4e1b53d 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,54 +4,61 @@ go 1.20 require ( github.com/alexflint/go-arg v1.4.3 + github.com/amacneil/dbmate/v2 v2.3.0 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/drexedam/gravatar v0.0.0-20210327211422-e94eea8c338e - github.com/fatih/color v1.13.0 - github.com/getsentry/sentry-go v0.17.0 + github.com/fatih/color v1.15.0 + github.com/getsentry/sentry-go v0.21.0 + github.com/glebarez/sqlite v1.8.0 github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/cors v1.2.1 github.com/go-chi/jwtauth v4.0.4+incompatible github.com/jc21/go-sse v0.0.0-20230307071053-2e6b1dbcb7ec github.com/jc21/jsref v0.0.0-20210608024405-a97debfc4760 - github.com/jmoiron/sqlx v1.3.5 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/qri-io/jsonschema v0.2.1 github.com/rotisserie/eris v0.5.4 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.8.2 github.com/vrischmann/envconfig v1.3.0 - golang.org/x/crypto v0.5.0 - modernc.org/sqlite v1.21.1 + golang.org/x/crypto v0.9.0 + gorm.io/datatypes v1.2.0 + gorm.io/driver/mysql v1.5.1 + gorm.io/driver/postgres v1.5.2 + gorm.io/gorm v1.25.1 + gorm.io/plugin/soft_delete v1.2.1 ) require ( github.com/alexflint/go-scalar v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dustin/go-humanize v1.0.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/glebarez/go-sqlite v1.21.1 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/lestrrat-go/jspointer v0.0.0-20181205001929-82fadba7561c // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/pdebug/v3 v3.0.1 // indirect github.com/lestrrat-go/structinfo v0.0.0-20210312050401-7f8bd69d6acb // indirect + github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.16 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/qri-io/jsonpointer v0.1.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/text v0.6.0 // indirect - golang.org/x/tools v0.1.12 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/uint128 v1.2.0 // indirect - modernc.org/cc/v3 v3.40.0 // indirect - modernc.org/ccgo/v3 v3.16.13 // indirect - modernc.org/libc v1.22.3 // indirect + modernc.org/libc v1.22.6 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect - modernc.org/opt v0.1.3 // indirect - modernc.org/strutil v1.1.3 // indirect - modernc.org/token v1.0.1 // indirect + modernc.org/sqlite v1.22.1 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index f56eb6f..64bed1c 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -3,6 +3,8 @@ github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258m github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/amacneil/dbmate/v2 v2.3.0 h1:2Q90DiJmOxLQGgGELm+hCrIJejGldx9R3jN2++FsHQI= +github.com/amacneil/dbmate/v2 v2.3.0/go.mod h1:Vwi1HgvTnkuJ0GLBzT/GWG0ZnOm870FQXQL9qshSswM= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible h1:Ppm0npCCsmuR9oQaBtRuZcmILVE74aXE+AmrJj8L2ns= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -13,12 +15,16 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/drexedam/gravatar v0.0.0-20210327211422-e94eea8c338e h1:2R8DvYLNr5DL25eWwpOdPno1eIbTNjJC0d7v8ti5cus= github.com/drexedam/gravatar v0.0.0-20210327211422-e94eea8c338e/go.mod h1:YjikoytuRI4q+GRd3xrOrKJN+Ayi2dwRomHLDDeMHfs= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/getsentry/sentry-go v0.17.0 h1:UustVWnOoDFHBS7IJUB2QK/nB5pap748ZEp0swnQJak= -github.com/getsentry/sentry-go v0.17.0/go.mod h1:B82dxtBvxG0KaPD8/hfSV+VcHD+Lg/xUS4JuQn1P4cM= +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/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/getsentry/sentry-go v0.21.0 h1:c9l5F1nPF30JIppulk4veau90PK6Smu3abgVtVQWon4= +github.com/getsentry/sentry-go v0.21.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/glebarez/go-sqlite v1.21.1 h1:7MZyUPh2XTrHS7xNEHQbrhfMZuPSzhkm2A1qgg0y5NY= +github.com/glebarez/go-sqlite v1.21.1/go.mod h1:ISs8MF6yk5cL4n/43rSOmVMGJJjHYr7L2MbZZ5Q4E2E= +github.com/glebarez/sqlite v1.8.0 h1:02X12E2I/4C1n+v90yTqrjRa8yuo7c3KeHI3FRznCvc= +github.com/glebarez/sqlite v1.8.0/go.mod h1:bpET16h1za2KOOMb8+jCp6UBP/iahDpfPQqSaYLTLx8= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= @@ -26,20 +32,32 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz github.com/go-chi/jwtauth v4.0.4+incompatible h1:LGIxg6YfvSBzxU2BljXbrzVc1fMlgqSKBQgKOGAVtPY= github.com/go-chi/jwtauth v4.0.4+incompatible/go.mod h1:Q5EIArY/QnD6BdS+IyDw7B2m6iNbnPxtfd6/BcmtWbs= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.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-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jc21/go-sse v0.0.0-20230307071053-2e6b1dbcb7ec h1:KKntwkZlM2w/88QiDyAeZ4th8grqtituzMW8qyapYzc= github.com/jc21/go-sse v0.0.0-20230307071053-2e6b1dbcb7ec/go.mod h1:4v5Xmm0eYuaWqKJ63XUV5YfQPoxtId3DgDytbnWhi+s= github.com/jc21/jsref v0.0.0-20210608024405-a97debfc4760 h1:7wxq2DIgtO36KLrFz1RldysO0WVvcYsD49G9tyAs01k= github.com/jc21/jsref v0.0.0-20210608024405-a97debfc4760/go.mod h1:yIq2t51OJgVsdRlPY68NAnyVdBH0kYXxDTFtUxOap80= -github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= -github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -56,21 +74,20 @@ github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCi github.com/lestrrat-go/structinfo v0.0.0-20190212233437-acd51874663b/go.mod h1:s2U6PowV3/Jobkx/S9d0XiPwOzs6niW3DIouw+7nZC8= github.com/lestrrat-go/structinfo v0.0.0-20210312050401-7f8bd69d6acb h1:DDg5u5lk2v8O8qxs8ecQkMUBj3tLW6wkSLzxxOyi1Ig= github.com/lestrrat-go/structinfo v0.0.0-20210312050401-7f8bd69d6acb/go.mod h1:i+E8Uf04vf2QjOWyJdGY75vmG+4rxiZW2kIj1lTB5mo= -github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= @@ -87,40 +104,43 @@ github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Ung github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rotisserie/eris v0.5.4 h1:Il6IvLdAapsMhvuOahHWiBnl1G++Q0/L5UIkI5mARSk= github.com/rotisserie/eris v0.5.4/go.mod h1:Z/kgYTJiJtocxCbFfvRmO+QejApzG6zpyky9G1A4g9s= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/vrischmann/envconfig v1.3.0 h1:4XIvQTXznxmWMnjouj0ST5lFo/WAYf5Exgl3x82crEk= github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI= github.com/wacul/ptr v1.0.0/go.mod h1:BD0gjsZrCwtoR+yWDB9v2hQ8STlq9tT84qKfa+3txOc= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJW5OokjraLLRURHiw0OzKHD/RNdspp4w= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210326220804-49726bf1d181/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -128,27 +148,26 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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= -lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= -modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= -modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= -modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= -modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= -modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= +gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco= +gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04= +gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= +gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c= +gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= +gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= +gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU= +gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk= +modernc.org/libc v1.22.6 h1:cbXU8R+A6aOjRuhsFh3nbDWXO/Hs4ClJRXYB11KmPDo= +modernc.org/libc v1.22.6/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU= -modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= -modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= -modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= -modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= +modernc.org/sqlite v1.22.1 h1:P2+Dhp5FR1RlVRkQ3dDfCiv3Ok8XPxqpe70IjYVA9oE= +modernc.org/sqlite v1.22.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= diff --git a/backend/internal/api/handler/access_lists.go b/backend/internal/api/handler/access_lists.go index 61c037d..b006a64 100644 --- a/backend/internal/api/handler/access_lists.go +++ b/backend/internal/api/handler/access_lists.go @@ -35,7 +35,7 @@ func GetAccessLists() func(http.ResponseWriter, *http.Request) { func GetAccessList() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var accessListID int + var accessListID uint if accessListID, err = getURLParamInt(r, "accessListID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -81,7 +81,7 @@ func CreateAccessList() func(http.ResponseWriter, *http.Request) { func UpdateAccessList() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var accessListID int + var accessListID uint if accessListID, err = getURLParamInt(r, "accessListID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -113,7 +113,7 @@ func UpdateAccessList() func(http.ResponseWriter, *http.Request) { func DeleteAccessList() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var accessListID int + var accessListID uint if accessListID, err = getURLParamInt(r, "accessListID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return diff --git a/backend/internal/api/handler/auth.go b/backend/internal/api/handler/auth.go index 18f2a98..23df31e 100644 --- a/backend/internal/api/handler/auth.go +++ b/backend/internal/api/handler/auth.go @@ -14,9 +14,9 @@ import ( ) type setAuthModel struct { - Type string `json:"type" db:"type"` - Secret string `json:"secret,omitempty" db:"secret"` - CurrentSecret string `json:"current_secret,omitempty"` + Type string + Secret string + CurrentSecret string } // SetAuth sets a auth method. This can be used for "me" and `2` for example diff --git a/backend/internal/api/handler/certificate_authorities.go b/backend/internal/api/handler/certificate_authorities.go index bd0a03e..b8c15b9 100644 --- a/backend/internal/api/handler/certificate_authorities.go +++ b/backend/internal/api/handler/certificate_authorities.go @@ -38,7 +38,7 @@ func GetCertificateAuthorities() func(http.ResponseWriter, *http.Request) { func GetCertificateAuthority() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var caID int + var caID uint if caID, err = getURLParamInt(r, "caID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -92,7 +92,7 @@ func CreateCertificateAuthority() func(http.ResponseWriter, *http.Request) { func UpdateCertificateAuthority() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var caID int + var caID uint if caID, err = getURLParamInt(r, "caID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -132,7 +132,7 @@ func UpdateCertificateAuthority() func(http.ResponseWriter, *http.Request) { func DeleteCertificateAuthority() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var caID int + var caID uint if caID, err = getURLParamInt(r, "caID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return diff --git a/backend/internal/api/handler/certificates.go b/backend/internal/api/handler/certificates.go index 5bba55b..014c592 100644 --- a/backend/internal/api/handler/certificates.go +++ b/backend/internal/api/handler/certificates.go @@ -55,7 +55,7 @@ func CreateCertificate() func(http.ResponseWriter, *http.Request) { var item certificate.Model if fillObjectFromBody(w, r, "", &item) { // Get userID from token - userID, _ := r.Context().Value(c.UserIDCtxKey).(int) + userID, _ := r.Context().Value(c.UserIDCtxKey).(uint) item.UserID = userID if err := item.Save(); err != nil { @@ -131,7 +131,7 @@ func DownloadCertificate() func(http.ResponseWriter, *http.Request) { // have a certificate id in the url. it will write errors to the output. func getCertificateFromRequest(w http.ResponseWriter, r *http.Request) *certificate.Model { var err error - var certificateID int + var certificateID uint if certificateID, err = getURLParamInt(r, "certificateID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return nil diff --git a/backend/internal/api/handler/dns_providers.go b/backend/internal/api/handler/dns_providers.go index 5320f98..40966ca 100644 --- a/backend/internal/api/handler/dns_providers.go +++ b/backend/internal/api/handler/dns_providers.go @@ -38,7 +38,7 @@ func GetDNSProviders() func(http.ResponseWriter, *http.Request) { func GetDNSProvider() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var providerID int + var providerID uint if providerID, err = getURLParamInt(r, "providerID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -87,7 +87,7 @@ func CreateDNSProvider() func(http.ResponseWriter, *http.Request) { func UpdateDNSProvider() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var providerID int + var providerID uint if providerID, err = getURLParamInt(r, "providerID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -122,7 +122,7 @@ func UpdateDNSProvider() func(http.ResponseWriter, *http.Request) { func DeleteDNSProvider() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var providerID int + var providerID uint if providerID, err = getURLParamInt(r, "providerID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return diff --git a/backend/internal/api/handler/helpers.go b/backend/internal/api/handler/helpers.go index ae47ebd..b12d7a0 100644 --- a/backend/internal/api/handler/helpers.go +++ b/backend/internal/api/handler/helpers.go @@ -4,7 +4,6 @@ import ( "net/http" "strconv" "strings" - "time" "npm/internal/api/context" "npm/internal/model" @@ -19,11 +18,6 @@ func getPageInfoFromRequest(r *http.Request) (model.PageInfo, error) { var pageInfo model.PageInfo var err error - pageInfo.FromDate, pageInfo.ToDate, err = getDateRanges(r) - if err != nil { - return pageInfo, err - } - pageInfo.Offset, pageInfo.Limit, err = getPagination(r) if err != nil { return pageInfo, err @@ -34,32 +28,6 @@ func getPageInfoFromRequest(r *http.Request) (model.PageInfo, error) { return pageInfo, nil } -func getDateRanges(r *http.Request) (time.Time, time.Time, error) { - queryValues := r.URL.Query() - from := queryValues.Get("from") - fromDate := time.Now().AddDate(0, -1, 0) // 1 month ago by default - to := queryValues.Get("to") - toDate := time.Now() - - if from != "" { - var fromErr error - fromDate, fromErr = time.Parse(time.RFC3339, from) - if fromErr != nil { - return fromDate, toDate, eris.Errorf("From date is not in correct format: %v", strings.ReplaceAll(time.RFC3339, "Z", "+")) - } - } - - if to != "" { - var toErr error - toDate, toErr = time.Parse(time.RFC3339, to) - if toErr != nil { - return fromDate, toDate, eris.Errorf("To date is not in correct format: %v", strings.ReplaceAll(time.RFC3339, "Z", "+")) - } - } - - return fromDate, toDate, nil -} - func getSortParameter(r *http.Request) []model.Sort { var sortFields []model.Sort @@ -132,12 +100,11 @@ func getQueryVarBool(r *http.Request, varName string, required bool, defaultValu } */ -func getURLParamInt(r *http.Request, varName string) (int, error) { +func getURLParamInt(r *http.Request, varName string) (uint, error) { + var defaultValue uint = 0 + required := true - defaultValue := 0 paramStr := chi.URLParam(r, varName) - var err error - var paramInt int if paramStr == "" && required { return 0, eris.Errorf("%v was not supplied in the request", varName) @@ -145,11 +112,13 @@ func getURLParamInt(r *http.Request, varName string) (int, error) { return defaultValue, nil } - if paramInt, err = strconv.Atoi(paramStr); err != nil { + // func ParseUint(s string, base int, bitSize int) (n uint64, err error) + paramUint, err := strconv.ParseUint(paramStr, 10, 32) + if err != nil { return 0, eris.Wrapf(err, "%v is not a valid number", varName) } - return paramInt, nil + return uint(paramUint), nil } func getURLParamString(r *http.Request, varName string) (string, error) { diff --git a/backend/internal/api/handler/hosts.go b/backend/internal/api/handler/hosts.go index daec999..2c6125b 100644 --- a/backend/internal/api/handler/hosts.go +++ b/backend/internal/api/handler/hosts.go @@ -40,7 +40,7 @@ func GetHosts() func(http.ResponseWriter, *http.Request) { func GetHost() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var hostID int + var hostID uint if hostID, err = getURLParamInt(r, "hostID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -74,7 +74,7 @@ func CreateHost() func(http.ResponseWriter, *http.Request) { } // Get userID from token - userID, _ := r.Context().Value(c.UserIDCtxKey).(int) + userID, _ := r.Context().Value(c.UserIDCtxKey).(uint) newHost.UserID = userID if err = validator.ValidateHost(newHost); err != nil { @@ -103,7 +103,7 @@ func CreateHost() func(http.ResponseWriter, *http.Request) { func UpdateHost() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var hostID int + var hostID uint if hostID, err = getURLParamInt(r, "hostID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -148,7 +148,7 @@ func UpdateHost() func(http.ResponseWriter, *http.Request) { func DeleteHost() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var hostID int + var hostID uint if hostID, err = getURLParamInt(r, "hostID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -173,7 +173,7 @@ func DeleteHost() func(http.ResponseWriter, *http.Request) { func GetHostNginxConfig(format string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var hostID int + var hostID uint if hostID, err = getURLParamInt(r, "hostID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return diff --git a/backend/internal/api/handler/nginx_templates.go b/backend/internal/api/handler/nginx_templates.go index b80caa1..32fb68f 100644 --- a/backend/internal/api/handler/nginx_templates.go +++ b/backend/internal/api/handler/nginx_templates.go @@ -36,7 +36,7 @@ func GetNginxTemplates() func(http.ResponseWriter, *http.Request) { func GetNginxTemplate() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var templateID int + var templateID uint if templateID, err = getURLParamInt(r, "templateID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -85,7 +85,7 @@ func CreateNginxTemplate() func(http.ResponseWriter, *http.Request) { func UpdateNginxTemplate() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var templateID int + var templateID uint if templateID, err = getURLParamInt(r, "templateID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -122,7 +122,7 @@ func UpdateNginxTemplate() func(http.ResponseWriter, *http.Request) { func DeleteNginxTemplate() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var templateID int + var templateID uint if templateID, err = getURLParamInt(r, "templateID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return diff --git a/backend/internal/api/handler/streams.go b/backend/internal/api/handler/streams.go index c3f8fa0..0c2b8fb 100644 --- a/backend/internal/api/handler/streams.go +++ b/backend/internal/api/handler/streams.go @@ -36,7 +36,7 @@ func GetStreams() func(http.ResponseWriter, *http.Request) { func GetStream() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var hostID int + var hostID uint if hostID, err = getURLParamInt(r, "hostID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -85,7 +85,7 @@ func CreateStream() func(http.ResponseWriter, *http.Request) { func UpdateStream() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var hostID int + var hostID uint if hostID, err = getURLParamInt(r, "hostID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -120,7 +120,7 @@ func UpdateStream() func(http.ResponseWriter, *http.Request) { func DeleteStream() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var hostID int + var hostID uint if hostID, err = getURLParamInt(r, "hostID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return diff --git a/backend/internal/api/handler/tokens.go b/backend/internal/api/handler/tokens.go index 1c8e889..58c483f 100644 --- a/backend/internal/api/handler/tokens.go +++ b/backend/internal/api/handler/tokens.go @@ -93,7 +93,7 @@ func RefreshToken() func(http.ResponseWriter, *http.Request) { // Route: POST /tokens/sse func NewSSEToken() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - userID := r.Context().Value(c.UserIDCtxKey).(int) + userID := r.Context().Value(c.UserIDCtxKey).(uint) // Find user userObj, userErr := user.GetByID(userID) diff --git a/backend/internal/api/handler/upstreams.go b/backend/internal/api/handler/upstreams.go index 7a470f9..3add697 100644 --- a/backend/internal/api/handler/upstreams.go +++ b/backend/internal/api/handler/upstreams.go @@ -41,7 +41,7 @@ func GetUpstreams() func(http.ResponseWriter, *http.Request) { func GetUpstream() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var upstreamID int + var upstreamID uint if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -75,7 +75,7 @@ func CreateUpstream() func(http.ResponseWriter, *http.Request) { } // Get userID from token - userID, _ := r.Context().Value(c.UserIDCtxKey).(int) + userID, _ := r.Context().Value(c.UserIDCtxKey).(uint) newUpstream.UserID = userID if err = validator.ValidateUpstream(newUpstream); err != nil { @@ -99,7 +99,7 @@ func CreateUpstream() func(http.ResponseWriter, *http.Request) { func UpdateUpstream() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var upstreamID int + var upstreamID uint if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -141,7 +141,7 @@ func UpdateUpstream() func(http.ResponseWriter, *http.Request) { func DeleteUpstream() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var upstreamID int + var upstreamID uint if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return @@ -172,7 +172,7 @@ func DeleteUpstream() func(http.ResponseWriter, *http.Request) { func GetUpstreamNginxConfig(format string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { var err error - var upstreamID int + var upstreamID uint if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return diff --git a/backend/internal/api/handler/users.go b/backend/internal/api/handler/users.go index f9b06f3..9755bc2 100644 --- a/backend/internal/api/handler/users.go +++ b/backend/internal/api/handler/users.go @@ -121,14 +121,14 @@ func UpdateUser() func(http.ResponseWriter, *http.Request) { // Route: DELETE /users/{userID} func DeleteUser() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - var userID int + var userID uint var err error if userID, err = getURLParamInt(r, "userID"); err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) return } - myUserID, _ := r.Context().Value(c.UserIDCtxKey).(int) + myUserID, _ := r.Context().Value(c.UserIDCtxKey).(uint) if myUserID == userID { h.ResultErrorJSON(w, r, http.StatusBadRequest, "You cannot delete yourself!", nil) return @@ -224,11 +224,11 @@ func DeleteUsers() func(http.ResponseWriter, *http.Request) { } } -func getUserIDFromRequest(r *http.Request) (int, bool, error) { +func getUserIDFromRequest(r *http.Request) (uint, bool, error) { userIDstr := chi.URLParam(r, "userID") - selfUserID, _ := r.Context().Value(c.UserIDCtxKey).(int) + selfUserID, _ := r.Context().Value(c.UserIDCtxKey).(uint) - var userID int + var userID uint self := false if userIDstr == "me" { // Get user id from Token diff --git a/backend/internal/api/middleware/auth.go b/backend/internal/api/middleware/auth.go index 65249f0..1cd91cf 100644 --- a/backend/internal/api/middleware/auth.go +++ b/backend/internal/api/middleware/auth.go @@ -48,7 +48,7 @@ func Enforce(permission string) func(http.Handler) http.Handler { return } - userID := int(claims["uid"].(float64)) + userID := uint(claims["uid"].(float64)) _, enabled := user.IsEnabled(userID) if token == nil || !token.Valid || !enabled { h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil) diff --git a/backend/internal/api/middleware/sse_auth.go b/backend/internal/api/middleware/sse_auth.go index 023e660..4644acc 100644 --- a/backend/internal/api/middleware/sse_auth.go +++ b/backend/internal/api/middleware/sse_auth.go @@ -21,7 +21,7 @@ func SSEAuth(next http.Handler) http.Handler { return } - userID := int(claims["uid"].(float64)) + userID := uint(claims["uid"].(float64)) _, enabled := user.IsEnabled(userID) if token == nil || !token.Valid || !enabled || !claims.VerifyIssuer("sse", true) { h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil) diff --git a/backend/internal/api/router.go b/backend/internal/api/router.go index 5d0a17c..e1be1cd 100644 --- a/backend/internal/api/router.go +++ b/backend/internal/api/router.go @@ -8,15 +8,6 @@ import ( "npm/internal/api/middleware" "npm/internal/api/schema" "npm/internal/config" - "npm/internal/entity/accesslist" - "npm/internal/entity/certificate" - "npm/internal/entity/certificateauthority" - "npm/internal/entity/dnsprovider" - "npm/internal/entity/host" - "npm/internal/entity/nginxtemplate" - "npm/internal/entity/setting" - "npm/internal/entity/stream" - "npm/internal/entity/upstream" "npm/internal/entity/user" "npm/internal/logger" "npm/internal/serverevents" @@ -102,7 +93,8 @@ func applyRoutes(r chi.Router) chi.Router { r.With(middleware.Enforce(user.CapabilityUsersManage)).Route("/", func(r chi.Router) { // List - r.With(middleware.Enforce(user.CapabilityUsersManage), middleware.Filters(user.GetFilterSchema())). + // r.With(middleware.Enforce(user.CapabilityUsersManage), middleware.Filters(user.GetFilterSchema())). + r.With(middleware.Enforce(user.CapabilityUsersManage)). Get("/", handler.GetUsers()) // Specific Item @@ -132,8 +124,8 @@ func applyRoutes(r chi.Router) chi.Router { // Settings r.With(middleware.EnforceSetup(true), middleware.Enforce(user.CapabilitySettingsManage)).Route("/settings", func(r chi.Router) { - r.With(middleware.Filters(setting.GetFilterSchema())). - Get("/", handler.GetSettings()) + // r.With(middleware.Filters(setting.GetFilterSchema())). + r.Get("/", handler.GetSettings()) r.Get("/{name}", handler.GetSetting()) r.With(middleware.EnforceRequestSchema(schema.CreateSetting())). Post("/", handler.CreateSetting()) @@ -144,7 +136,8 @@ func applyRoutes(r chi.Router) chi.Router { // Access Lists r.With(middleware.EnforceSetup(true)).Route("/access-lists", func(r chi.Router) { // List - r.With(middleware.Filters(accesslist.GetFilterSchema()), middleware.Enforce(user.CapabilityAccessListsView)). + // r.With(middleware.Filters(accesslist.GetFilterSchema()), middleware.Enforce(user.CapabilityAccessListsView)). + r.With(middleware.Enforce(user.CapabilityAccessListsView)). Get("/", handler.GetAccessLists()) // Create @@ -166,7 +159,8 @@ func applyRoutes(r chi.Router) chi.Router { // DNS Providers r.With(middleware.EnforceSetup(true)).Route("/dns-providers", func(r chi.Router) { // List - r.With(middleware.Enforce(user.CapabilityDNSProvidersView), middleware.Filters(dnsprovider.GetFilterSchema())). + // r.With(middleware.Enforce(user.CapabilityDNSProvidersView), middleware.Filters(dnsprovider.GetFilterSchema())). + r.With(middleware.Enforce(user.CapabilityDNSProvidersView)). Get("/", handler.GetDNSProviders()) // Create @@ -194,7 +188,8 @@ func applyRoutes(r chi.Router) chi.Router { // Certificate Authorities r.With(middleware.EnforceSetup(true)).Route("/certificate-authorities", func(r chi.Router) { // List - r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesView), middleware.Filters(certificateauthority.GetFilterSchema())). + // r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesView), middleware.Filters(certificateauthority.GetFilterSchema())). + r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesView)). Get("/", handler.GetCertificateAuthorities()) // Create @@ -216,7 +211,8 @@ func applyRoutes(r chi.Router) chi.Router { // Certificates r.With(middleware.EnforceSetup(true)).Route("/certificates", func(r chi.Router) { // List - r.With(middleware.Enforce(user.CapabilityCertificatesView), middleware.Filters(certificate.GetFilterSchema())). + // r.With(middleware.Enforce(user.CapabilityCertificatesView), middleware.Filters(certificate.GetFilterSchema())). + r.With(middleware.Enforce(user.CapabilityCertificatesView)). Get("/", handler.GetCertificates()) // Create @@ -241,7 +237,8 @@ func applyRoutes(r chi.Router) chi.Router { // Hosts r.With(middleware.EnforceSetup(true)).Route("/hosts", func(r chi.Router) { // List - r.With(middleware.Enforce(user.CapabilityHostsView), middleware.Filters(host.GetFilterSchema())). + // r.With(middleware.Enforce(user.CapabilityHostsView), middleware.Filters(host.GetFilterSchema())). + r.With(middleware.Enforce(user.CapabilityHostsView)). Get("/", handler.GetHosts()) // Create @@ -265,7 +262,8 @@ func applyRoutes(r chi.Router) chi.Router { // Nginx Templates r.With(middleware.EnforceSetup(true)).Route("/nginx-templates", func(r chi.Router) { // List - r.With(middleware.Enforce(user.CapabilityNginxTemplatesView), middleware.Filters(nginxtemplate.GetFilterSchema())). + // r.With(middleware.Enforce(user.CapabilityNginxTemplatesView), middleware.Filters(nginxtemplate.GetFilterSchema())). + r.With(middleware.Enforce(user.CapabilityNginxTemplatesView)). Get("/", handler.GetNginxTemplates()) // Create @@ -287,7 +285,8 @@ func applyRoutes(r chi.Router) chi.Router { // Streams r.With(middleware.EnforceSetup(true)).Route("/streams", func(r chi.Router) { // List - r.With(middleware.Enforce(user.CapabilityStreamsView), middleware.Filters(stream.GetFilterSchema())). + // r.With(middleware.Enforce(user.CapabilityStreamsView), middleware.Filters(stream.GetFilterSchema())). + r.With(middleware.Enforce(user.CapabilityStreamsView)). Get("/", handler.GetStreams()) // Create @@ -309,7 +308,8 @@ func applyRoutes(r chi.Router) chi.Router { // Upstreams r.With(middleware.EnforceSetup(true)).Route("/upstreams", func(r chi.Router) { // List - r.With(middleware.Enforce(user.CapabilityHostsView), middleware.Filters(upstream.GetFilterSchema())). + // r.With(middleware.Enforce(user.CapabilityHostsView), middleware.Filters(upstream.GetFilterSchema())). + r.With(middleware.Enforce(user.CapabilityHostsView)). Get("/", handler.GetUpstreams()) // Create diff --git a/backend/internal/config/acmesh.go b/backend/internal/config/acmesh.go new file mode 100644 index 0000000..2d3ef39 --- /dev/null +++ b/backend/internal/config/acmesh.go @@ -0,0 +1,16 @@ +package config + +import ( + "fmt" +) + +type acmesh struct { + Home string `json:"home" envconfig:"optional,default=/data/.acme.sh"` + ConfigHome string `json:"config_home" envconfig:"optional,default=/data/.acme.sh/config"` + CertHome string `json:"cert_home" envconfig:"optional,default=/data/.acme.sh/certs"` +} + +// GetWellknown returns the well known path +func (a *acmesh) GetWellknown() string { + return fmt.Sprintf("%s/.well-known", a.Home) +} diff --git a/backend/internal/config/db.go b/backend/internal/config/db.go new file mode 100644 index 0000000..55dd92b --- /dev/null +++ b/backend/internal/config/db.go @@ -0,0 +1,79 @@ +package config + +import ( + "fmt" + "strings" +) + +const ( + DatabaseSqlite = "sqlite" + DatabasePostgres = "postgres" + DatabaseMysql = "mysql" +) + +type db struct { + Driver string `json:"driver" envconfig:"optional,default=sqlite"` + Host string `json:"host" envconfig:"optional,default="` + Port int `json:"port" envconfig:"optional,default="` + Username string `json:"username" envconfig:"optional,default="` + Password string `json:"password" envconfig:"optional,default="` + Name string `json:"name" envconfig:"optional,default="` + SSLMode string `json:"sslmode" envconfig:"optional,default=deisable"` +} + +// GetDriver returns the lowercase driver name +func (d *db) GetDriver() string { + return strings.ToLower(d.Driver) +} + +// GetGormConnectURL is used by Gorm +func (d *db) GetGormConnectURL() string { + switch d.GetDriver() { + case DatabaseSqlite: + return fmt.Sprintf("%s/nginxproxymanager.db", Configuration.DataFolder) + case DatabasePostgres: + return fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=UTC", + d.Host, + d.Username, + d.Password, + d.Name, + d.Port, + d.SSLMode, + ) + case DatabaseMysql: + return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", + d.Username, + d.Password, + d.Host, + d.Port, + d.Name, + ) + } + return "" +} + +// GetDBMateConnectURL is used by Dbmate +func (d *db) GetDBMateConnectURL() string { + switch d.GetDriver() { + case DatabaseSqlite: + return fmt.Sprintf("sqlite:%s/nginxproxymanager.db", Configuration.DataFolder) + case DatabasePostgres: + return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s", + d.Username, + d.Password, + d.Host, + d.Port, + d.Name, + d.SSLMode, + ) + case DatabaseMysql: + return fmt.Sprintf("mysql://%s:%s@%s:%d/%s", + d.Username, + d.Password, + d.Host, + d.Port, + d.Name, + ) + } + return "" +} diff --git a/backend/internal/config/vars.go b/backend/internal/config/vars.go index ff0a23d..6103e3c 100644 --- a/backend/internal/config/vars.go +++ b/backend/internal/config/vars.go @@ -1,7 +1,6 @@ package config import ( - "fmt" "npm/internal/logger" ) @@ -30,22 +29,12 @@ type log struct { Format string `json:"format" envconfig:"optional,default=nice"` } -type acmesh struct { - Home string `json:"home" envconfig:"optional,default=/data/.acme.sh"` - ConfigHome string `json:"config_home" envconfig:"optional,default=/data/.acme.sh/config"` - CertHome string `json:"cert_home" envconfig:"optional,default=/data/.acme.sh/certs"` -} - // Configuration is the main configuration object var Configuration struct { DataFolder string `json:"data_folder" envconfig:"optional,default=/data"` DisableIPV4 bool `json:"disable_ipv4" envconfig:"optional"` DisableIPV6 bool `json:"disable_ipv6" envconfig:"optional"` Acmesh acmesh `json:"acmesh"` + DB db `json:"db"` Log log `json:"log"` } - -// GetWellknown returns the well known path -func (a *acmesh) GetWellknown() string { - return fmt.Sprintf("%s/.well-known", a.Home) -} diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go new file mode 100644 index 0000000..8abef95 --- /dev/null +++ b/backend/internal/database/db.go @@ -0,0 +1,80 @@ +package database + +import ( + "fmt" + "strings" + + "npm/internal/config" + "npm/internal/logger" + + "github.com/glebarez/sqlite" + "github.com/rotisserie/eris" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/schema" +) + +var dbInstance *gorm.DB + +// NewDB creates a new connection +func NewDB() { + logger.Info("Creating new DB instance using %s", strings.ToLower(config.Configuration.DB.Driver)) + db, err := connect() + if err != nil { + logger.Error("DatabaseConnectError", err) + } else if db != nil { + dbInstance = db + } +} + +// GetDB returns an existing or new instance +func GetDB() *gorm.DB { + if dbInstance == nil { + NewDB() + } + return dbInstance +} + +func connect() (*gorm.DB, error) { + var d gorm.Dialector + dsn := config.Configuration.DB.GetGormConnectURL() + switch strings.ToLower(config.Configuration.DB.Driver) { + + case config.DatabaseSqlite: + // autocreate(dsn) + d = sqlite.Open(dsn) + + case config.DatabasePostgres: + d = postgres.Open(dsn) + + case config.DatabaseMysql: + d = mysql.Open(dsn) + + default: + return nil, eris.New(fmt.Sprintf("Database driver %s is not supported. Valid options are: %s, %s or %s", config.Configuration.DB.Driver, config.DatabaseSqlite, config.DatabasePostgres, config.DatabaseMysql)) + } + + return gorm.Open(d, &gorm.Config{ + // see: https://gorm.io/docs/gorm_config.html + NamingStrategy: schema.NamingStrategy{ + SingularTable: true, + NoLowerCase: true, + }, + PrepareStmt: false, + }) +} + +/* +func autocreate(dbFile string) { + if _, err := os.Stat(dbFile); os.IsNotExist(err) { + // Create it + logger.Info("Creating Sqlite DB: %s", dbFile) + // nolint: gosec + _, err = os.Create(dbFile) + if err != nil { + logger.Error("FileCreateError", err) + } + } +} +*/ diff --git a/backend/internal/database/helpers.go b/backend/internal/database/helpers.go index 7553af3..0cd7447 100644 --- a/backend/internal/database/helpers.go +++ b/backend/internal/database/helpers.go @@ -1,46 +1,8 @@ package database -import ( - "fmt" - "strings" - - "npm/internal/errors" - "npm/internal/model" - "npm/internal/util" -) - const ( // DateFormat for DateFormat DateFormat = "2006-01-02" // DateTimeFormat for DateTimeFormat DateTimeFormat = "2006-01-02T15:04:05" ) - -// GetByQuery returns a row given a query, populating the model given -func GetByQuery(model interface{}, query string, params []interface{}) error { - db := GetInstance() - if db != nil { - err := db.Get(model, query, params...) - return err - } - - return errors.ErrDatabaseUnavailable -} - -// BuildOrderBySQL takes a `Sort` slice and constructs a query fragment -func BuildOrderBySQL(columns []string, sort *[]model.Sort) (string, []model.Sort) { - var sortStrings []string - var newSort []model.Sort - for _, sortItem := range *sort { - if util.SliceContainsItem(columns, sortItem.Field) { - sortStrings = append(sortStrings, fmt.Sprintf("`%s` %s", sortItem.Field, sortItem.Direction)) - newSort = append(newSort, sortItem) - } - } - - if len(sortStrings) > 0 { - return fmt.Sprintf("ORDER BY %s", strings.Join(sortStrings, ", ")), newSort - } - - return "", newSort -} diff --git a/backend/internal/database/migrator.go b/backend/internal/database/migrator.go index f90d927..d8c46f6 100644 --- a/backend/internal/database/migrator.go +++ b/backend/internal/database/migrator.go @@ -1,203 +1,44 @@ package database import ( - "database/sql" "fmt" - "io/fs" - "path" - "path/filepath" - "strings" - "sync" - "time" + "net/url" "npm/embed" + "npm/internal/config" "npm/internal/logger" - "npm/internal/util" - "github.com/jmoiron/sqlx" - "github.com/rotisserie/eris" + "github.com/amacneil/dbmate/v2/pkg/dbmate" + _ "github.com/amacneil/dbmate/v2/pkg/driver/postgres" + _ "github.com/amacneil/dbmate/v2/pkg/driver/sqlite" ) -// MigrationConfiguration options for the migrator. -type MigrationConfiguration struct { - Table string `json:"table"` - mux sync.Mutex -} - -// Default migrator configuration -var mConfiguration = MigrationConfiguration{ - Table: "migration", -} - -// ConfigureMigrator and will return error if missing required fields. -func ConfigureMigrator(c *MigrationConfiguration) error { - // ensure updates to the config are atomic - mConfiguration.mux.Lock() - defer mConfiguration.mux.Unlock() - if c == nil { - return eris.Errorf("a non nil Configuration is mandatory") - } - if strings.TrimSpace(c.Table) != "" { - mConfiguration.Table = c.Table - } - mConfiguration.Table = c.Table - return nil -} - type afterMigrationComplete func() -// Migrate will perform the migration from start to finish +// Migrate will bring the db up to date func Migrate(followup afterMigrationComplete) bool { - logger.Info("Migration: Started") + dbURL := config.Configuration.DB.GetDBMateConnectURL() + u, _ := url.Parse(dbURL) + db := dbmate.New(u) + db.FS = embed.MigrationFiles + db.MigrationsDir = []string{fmt.Sprintf("./migrations/%s", config.Configuration.DB.GetDriver())} - // Try to connect to the database sleeping for 15 seconds in between - var db *sqlx.DB - for { - db = GetInstance() - if db == nil { - logger.Warn("Database is unavailable for migration, retrying in 15 seconds") - time.Sleep(15 * time.Second) - } else { - break - } - } - - // Check for migration table existence - if !tableExists(db, mConfiguration.Table) { - err := createMigrationTable(db) - if err != nil { - logger.Error("MigratorError", err) - return false - } - logger.Info("Migration: Migration Table created") - } - - // DO MIGRATION - migrationCount, migrateErr := performFileMigrations(db) - if migrateErr != nil { - logger.Error("MigratorError", migrateErr) - } - - if migrateErr == nil { - logger.Info("Migration: Completed %v migration files", migrationCount) - followup() - return true - } - return false -} - -// createMigrationTable performs a query to create the migration table -// with the name specified in the configuration -func createMigrationTable(db *sqlx.DB) error { - logger.Info("Migration: Creating Migration Table: %v", mConfiguration.Table) - // nolint:lll - query := fmt.Sprintf("CREATE TABLE IF NOT EXISTS `%v` (filename TEXT PRIMARY KEY, migrated_on INTEGER NOT NULL DEFAULT 0)", mConfiguration.Table) - _, err := db.Exec(query) - return err -} - -// tableExists will check the database for the existence of the specified table. -func tableExists(db *sqlx.DB, tableName string) bool { - query := `SELECT CASE name WHEN $1 THEN true ELSE false END AS found FROM sqlite_master WHERE type='table' AND name = $1` - - row := db.QueryRowx(query, tableName) - if row == nil { - logger.Error("MigratorError", eris.Errorf("Cannot check if table exists, no row returned: %v", tableName)) - return false - } - - var exists *bool - if err := row.Scan(&exists); err != nil { - if err == sql.ErrNoRows { - return false - } - logger.Error("MigratorError", err) - return false - } - return *exists -} - -// performFileMigrations will perform the actual migration, -// importing files and updating the database with the rows imported. -func performFileMigrations(db *sqlx.DB) (int, error) { - var importedCount = 0 - - // Grab a list of previously ran migrations from the database: - previousMigrations, prevErr := getPreviousMigrations(db) - if prevErr != nil { - return importedCount, prevErr - } - - // List up the ".sql" files on disk - err := fs.WalkDir(embed.MigrationFiles, ".", func(file string, d fs.DirEntry, err error) error { - if !d.IsDir() { - shortFile := filepath.Base(file) - - // Check if this file already exists in the previous migrations - // and if so, ignore it - if util.SliceContainsItem(previousMigrations, shortFile) { - return nil - } - - logger.Info("Migration: Importing %v", shortFile) - - sqlContents, ioErr := embed.MigrationFiles.ReadFile(path.Clean(file)) - if ioErr != nil { - return ioErr - } - - sqlString := string(sqlContents) - - tx := db.MustBegin() - if _, execErr := tx.Exec(sqlString); execErr != nil { - return execErr - } - if commitErr := tx.Commit(); commitErr != nil { - return commitErr - } - if markErr := markMigrationSuccessful(db, shortFile); markErr != nil { - return markErr - } - - importedCount++ - } - return nil - }) - - return importedCount, err -} - -// getPreviousMigrations will query the migration table for names -// of migrations we can ignore because they should have already -// been imported -func getPreviousMigrations(db *sqlx.DB) ([]string, error) { - var existingMigrations []string - // nolint:gosec - query := fmt.Sprintf("SELECT filename FROM `%v` ORDER BY filename", mConfiguration.Table) - rows, err := db.Queryx(query) + migrations, err := db.FindMigrations() if err != nil { - if err == sql.ErrNoRows { - return existingMigrations, nil - } - return existingMigrations, err + logger.Error("MigrationError", err) + return false } - for rows.Next() { - var filename *string - err := rows.Scan(&filename) - if err != nil { - return existingMigrations, err - } - existingMigrations = append(existingMigrations, *filename) + for _, m := range migrations { + logger.Debug("%s: %s", m.Version, m.FilePath) } - return existingMigrations, nil -} + err = db.CreateAndMigrate() + if err != nil { + logger.Error("MigrationError", err) + return false + } -// markMigrationSuccessful will add a row to the migration table -func markMigrationSuccessful(db *sqlx.DB, filename string) error { - // nolint:gosec - query := fmt.Sprintf("INSERT INTO `%v` (filename) VALUES ($1)", mConfiguration.Table) - _, err := db.Exec(query, filename) - return err + followup() + return true } diff --git a/backend/internal/database/setup.go b/backend/internal/database/setup.go deleted file mode 100644 index b4101db..0000000 --- a/backend/internal/database/setup.go +++ /dev/null @@ -1,37 +0,0 @@ -package database - -import ( - "database/sql" - - "npm/internal/config" - "npm/internal/errors" - "npm/internal/logger" -) - -// CheckSetup Quick check by counting the number of users in the database -func CheckSetup() { - query := `SELECT COUNT(*) FROM "user" WHERE is_deleted = $1 AND is_disabled = $1 AND is_system = $1` - db := GetInstance() - - if db != nil { - row := db.QueryRowx(query, false) - var totalRows int - queryErr := row.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Error("SetupError", queryErr) - return - } - if totalRows == 0 { - logger.Warn("No users found, starting in Setup Mode") - } else { - config.IsSetup = true - logger.Info("Application is setup") - } - - if config.ErrorReporting { - logger.Warn("Error reporting is enabled - Application Errors WILL be sent to Sentry, you can disable this in the Settings interface") - } - } else { - logger.Error("DatabaseError", errors.ErrDatabaseUnavailable) - } -} diff --git a/backend/internal/database/sqlite.go b/backend/internal/database/sqlite.go deleted file mode 100644 index 22a5083..0000000 --- a/backend/internal/database/sqlite.go +++ /dev/null @@ -1,74 +0,0 @@ -package database - -import ( - "fmt" - "os" - - "npm/internal/config" - "npm/internal/logger" - - "github.com/jmoiron/sqlx" - - // Blank import for Sqlite - _ "modernc.org/sqlite" -) - -var dbInstance *sqlx.DB - -// NewDB creates a new connection -func NewDB() { - logger.Info("Creating new DB instance") - db := SqliteDB() - if db != nil { - dbInstance = db - } -} - -// GetInstance returns an existing or new instance -func GetInstance() *sqlx.DB { - if dbInstance == nil { - NewDB() - } else if err := dbInstance.Ping(); err != nil { - NewDB() - } - - return dbInstance -} - -// SqliteDB Create sqlite client -func SqliteDB() *sqlx.DB { - dbFile := fmt.Sprintf("%s/nginxproxymanager.db", config.Configuration.DataFolder) - autocreate(dbFile) - db, err := sqlx.Open("sqlite", dbFile) - if err != nil { - logger.Error("SqliteError", err) - return nil - } - - return db -} - -// Commit will close and reopen the db file -func Commit() *sqlx.DB { - if dbInstance != nil { - err := dbInstance.Close() - if err != nil { - logger.Error("DatabaseCloseError", err) - } - } - NewDB() - return dbInstance -} - -func autocreate(dbFile string) { - if _, err := os.Stat(dbFile); os.IsNotExist(err) { - // Create it - logger.Info("Creating Sqlite DB: %s", dbFile) - // nolint: gosec - _, err = os.Create(dbFile) - if err != nil { - logger.Error("FileCreateError", err) - } - Commit() - } -} diff --git a/backend/internal/entity/accesslist/filters.go b/backend/internal/entity/accesslist/filters.go deleted file mode 100644 index 5c1bd1b..0000000 --- a/backend/internal/entity/accesslist/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package accesslist - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/accesslist/methods.go b/backend/internal/entity/accesslist/methods.go index bce8bf3..b75dc19 100644 --- a/backend/internal/entity/accesslist/methods.go +++ b/backend/internal/entity/accesslist/methods.go @@ -1,122 +1,41 @@ package accesslist import ( - "database/sql" - "fmt" - - "npm/internal/database" "npm/internal/entity" - "npm/internal/errors" - "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a row by ID -func GetByID(id int) (Model, error) { +func GetByID(id uint) (Model, error) { var m Model err := m.LoadByID(id) return m, err } -// Create will create a row from this model -func Create(m *Model) (int, error) { - if m.ID != 0 { - return 0, eris.New("Cannot create access list when model already has an ID") - } - - m.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( - created_on, - modified_on, - user_id, - name, - meta, - is_deleted - ) VALUES ( - :created_on, - :modified_on, - :user_id, - :name, - :meta, - :is_deleted - )`, m) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - return int(last), nil -} - -// Update will Update a row from this model -func Update(m *Model) error { - if m.ID == 0 { - return eris.New("Cannot update access list when model doesn't have an ID") - } - - m.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - user_id = :user_id, - name = :name, - meta = :meta, - is_deleted = :is_deleted - WHERE id = :id`, m) - - return err -} - // List will return a list of access lists -func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "name", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Error("ListAccessListsError", queryErr) - logger.Debug("%s -- %+v", query, params) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Error("ListAccessListsError", err) - logger.Debug("%s -- %+v", query, params) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, diff --git a/backend/internal/entity/accesslist/model.go b/backend/internal/entity/accesslist/model.go index 55ed0ca..342236d 100644 --- a/backend/internal/entity/accesslist/model.go +++ b/backend/internal/entity/accesslist/model.go @@ -1,77 +1,54 @@ package accesslist import ( - "fmt" - "time" - "npm/internal/database" + "npm/internal/entity" "npm/internal/entity/user" "npm/internal/types" "github.com/rotisserie/eris" ) -const ( - tableName = "access_list" -) - -// Model is the access list model +// Model is the model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` - Name string `json:"name" db:"name" filter:"name,string"` - Meta types.JSONB `json:"meta" db:"meta"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + UserID int `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` + Name string `json:"name" gorm:"column:name" filter:"name,string"` + Meta types.JSONB `json:"meta" gorm:"column:meta"` // Expansions - User *user.Model `json:"user,omitempty"` + User *user.Model `json:"user,omitempty" gorm:"-"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "access_list" } // LoadByID will load from an ID -func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) - params := []interface{}{id, 0} - return m.getByQuery(query, params) -} - -// Touch will update model's timestamp(s) -func (m *Model) Touch(created bool) { - var d types.DBDate - d.Time = time.Now() - if created { - m.CreatedOn = d - } - m.ModifiedOn = d +func (m *Model) LoadByID(id uint) error { + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // Save will save this model to the DB func (m *Model) Save() error { - var err error - if m.UserID == 0 { return eris.Errorf("User ID must be specified") } - if m.ID == 0 { - m.ID, err = Create(m) - } else { - err = Update(m) - } - - return err + db := database.GetDB() + result := db.Save(m) + return result.Error } -// Delete will mark a access list as deleted +// Delete will mark row as deleted func (m *Model) Delete() bool { - m.Touch(false) - m.IsDeleted = true - if err := m.Save(); err != nil { + if m.ID == 0 { + // Can't delete a new object return false } - return true + db := database.GetDB() + result := db.Delete(m) + return result.Error == nil } diff --git a/backend/internal/entity/accesslist/structs.go b/backend/internal/entity/accesslist/structs.go deleted file mode 100644 index b755b74..0000000 --- a/backend/internal/entity/accesslist/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package accesslist - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for the list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/entity/auth/methods.go b/backend/internal/entity/auth/methods.go index a2497fc..c711f30 100644 --- a/backend/internal/entity/auth/methods.go +++ b/backend/internal/entity/auth/methods.go @@ -1,11 +1,7 @@ package auth import ( - "fmt" - "npm/internal/database" - - "github.com/rotisserie/eris" ) // GetByID finds a auth by ID @@ -16,12 +12,17 @@ func GetByID(id int) (Model, error) { } // GetByUserIDType finds a user by email -func GetByUserIDType(userID int, authType string) (Model, error) { - var m Model - err := m.LoadByUserIDType(userID, authType) - return m, err +func GetByUserIDType(userID uint, authType string) (Model, error) { + var auth Model + db := database.GetDB() + result := db. + Where("user_id = ?", userID). + Where("type = ?", authType). + First(&auth) + return auth, result.Error } +/* // Create will create a Auth from this model func Create(auth *Model) (int, error) { if auth.ID != 0 { @@ -59,7 +60,9 @@ func Create(auth *Model) (int, error) { return int(last), nil } +*/ +/* // Update will Update a Auth from this model func Update(auth *Model) error { if auth.ID == 0 { @@ -81,3 +84,4 @@ func Update(auth *Model) error { return err } +*/ diff --git a/backend/internal/entity/auth/model.go b/backend/internal/entity/auth/model.go index d92cfa7..17ca419 100644 --- a/backend/internal/entity/auth/model.go +++ b/backend/internal/entity/auth/model.go @@ -1,52 +1,49 @@ package auth import ( - "fmt" - "time" - "npm/internal/database" - "npm/internal/types" + "npm/internal/entity" "github.com/rotisserie/eris" "golang.org/x/crypto/bcrypt" ) const ( - tableName = "auth" - // TypePassword is the Password Type TypePassword = "password" ) -// Model is the user model +// Model is the model type Model struct { - ID int `json:"id" db:"id"` - UserID int `json:"user_id" db:"user_id"` - Type string `json:"type" db:"type"` - Secret string `json:"secret,omitempty" db:"secret"` - CreatedOn types.DBDate `json:"created_on" db:"created_on"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + UserID uint `json:"user_id" gorm:"column:user_id"` + Type string `json:"type" gorm:"column:type;default:password"` + Secret string `json:"secret,omitempty" gorm:"column:secret"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "auth" } // LoadByID will load from an ID func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? LIMIT 1", tableName) - params := []interface{}{id} - return m.getByQuery(query, params) + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // LoadByUserIDType will load from an ID func (m *Model) LoadByUserIDType(userID int, authType string) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE user_id = ? AND type = ? LIMIT 1", tableName) - params := []interface{}{userID, authType} - return m.getByQuery(query, params) + db := database.GetDB() + result := db. + Where("user_id = ?", userID). + Where("type = ?", authType). + First(&m) + return result.Error } +/* // Touch will update model's timestamp(s) func (m *Model) Touch(created bool) { var d types.DBDate @@ -56,18 +53,14 @@ func (m *Model) Touch(created bool) { } m.ModifiedOn = d } +*/ // Save will save this model to the DB func (m *Model) Save() error { - var err error - - if m.ID == 0 { - m.ID, err = Create(m) - } else { - err = Update(m) - } - - return err + db := database.GetDB() + // todo: touch? not sure that save does this or not? + result := db.Save(m) + return result.Error } // SetPassword will generate a hashed password based on given string diff --git a/backend/internal/entity/capability.go b/backend/internal/entity/capability.go new file mode 100644 index 0000000..8b9d7f0 --- /dev/null +++ b/backend/internal/entity/capability.go @@ -0,0 +1,11 @@ +package entity + +// Capability is the db model +type Capability struct { + Name string `json:"name" gorm:"column:name;primaryKey" filter:"name,string"` +} + +// TableName overrides the table name used by gorm +func (Capability) TableName() string { + return "capability" +} diff --git a/backend/internal/entity/certificate/filters.go b/backend/internal/entity/certificate/filters.go deleted file mode 100644 index 1c034c1..0000000 --- a/backend/internal/entity/certificate/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package certificate - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/certificate/methods.go b/backend/internal/entity/certificate/methods.go index 70a2810..10bf53a 100644 --- a/backend/internal/entity/certificate/methods.go +++ b/backend/internal/entity/certificate/methods.go @@ -1,142 +1,54 @@ package certificate import ( - "database/sql" - "fmt" - "npm/internal/database" "npm/internal/entity" - "npm/internal/errors" "npm/internal/jobqueue" "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a row by ID -func GetByID(id int) (Model, error) { +func GetByID(id uint) (Model, error) { var m Model err := m.LoadByID(id) return m, err } -// Create will create a row from this model -func Create(certificate *Model) (int, error) { - if certificate.ID != 0 { - return 0, eris.New("Cannot create certificate when model already has an ID") - } - - certificate.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( - created_on, - modified_on, - user_id, - type, - certificate_authority_id, - dns_provider_id, - name, - domain_names, - expires_on, - status, - error_message, - meta, - is_ecc, - is_deleted - ) VALUES ( - :created_on, - :modified_on, - :user_id, - :type, - :certificate_authority_id, - :dns_provider_id, - :name, - :domain_names, - :expires_on, - :status, - :error_message, - :meta, - :is_ecc, - :is_deleted - )`, certificate) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - return int(last), nil -} - -// Update will Update a Auth from this model -func Update(certificate *Model) error { - if certificate.ID == 0 { - return eris.New("Cannot update certificate when model doesn't have an ID") - } - - certificate.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - type = :type, - user_id = :user_id, - certificate_authority_id = :certificate_authority_id, - dns_provider_id = :dns_provider_id, - name = :name, - domain_names = :domain_names, - expires_on = :expires_on, - status = :status, - error_message = :error_message, - meta = :meta, - is_ecc = :is_ecc, - is_deleted = :is_deleted - WHERE id = :id`, certificate) - - return err +// GetByStatus will select rows that are ready for requesting +func GetByStatus(status string) ([]Model, error) { + items := make([]Model, 0) + db := database.GetDB() + result := db. + Joins("INNER JOIN certificate_authority ON certificate_authority.id = certificate.certificate_authority_id AND certificate_authority.is_deleted = ?", 0). + Where("type IN ?", []string{"http", "dns"}). + Where("status = ?", status). + Where("certificate_authority_id > ?", 0). + Find(&items) + return items, result.Error } // List will return a list of certificates -func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "name", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Debug("%s -- %+v", query, params) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Debug("%s -- %+v", query, params) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } if expand != nil { @@ -148,7 +60,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis } } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, @@ -160,33 +72,6 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis return result, nil } -// GetByStatus will select rows that are ready for requesting -func GetByStatus(status string) ([]Model, error) { - models := make([]Model, 0) - db := database.GetInstance() - - query := fmt.Sprintf(` - SELECT - t.* - FROM "%s" t - INNER JOIN "certificate_authority" c ON c."id" = t."certificate_authority_id" - WHERE - t."type" IN ("http", "dns") AND - t."status" = ? AND - t."certificate_authority_id" > 0 AND - t."is_deleted" = 0 - `, tableName) - - params := []interface{}{StatusReady} - err := db.Select(&models, query, params...) - if err != nil && err != sql.ErrNoRows { - logger.Error("GetByStatusError", err) - logger.Debug("Query: %s -- %+v", query, params) - } - - return models, err -} - // AddPendingJobs is intended to be used at startup to add // anything pending to the JobQueue just once, based on // the database row status diff --git a/backend/internal/entity/certificate/model.go b/backend/internal/entity/certificate/model.go index 597458e..2b6f647 100644 --- a/backend/internal/entity/certificate/model.go +++ b/backend/internal/entity/certificate/model.go @@ -10,6 +10,7 @@ import ( "npm/internal/acme" "npm/internal/config" "npm/internal/database" + "npm/internal/entity" "npm/internal/entity/certificateauthority" "npm/internal/entity/dnsprovider" "npm/internal/entity/user" @@ -22,8 +23,6 @@ import ( ) const ( - tableName = "certificate" - // TypeCustom custom cert type TypeCustom = "custom" // TypeHTTP http cert type @@ -43,54 +42,40 @@ const ( StatusProvided = "provided" ) -// Model is the user model +// Model is the model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - ExpiresOn types.NullableDBDate `json:"expires_on" db:"expires_on" filter:"expires_on,integer"` - Type string `json:"type" db:"type" filter:"type,string"` - UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` - CertificateAuthorityID int `json:"certificate_authority_id" db:"certificate_authority_id" filter:"certificate_authority_id,integer"` - DNSProviderID int `json:"dns_provider_id" db:"dns_provider_id" filter:"dns_provider_id,integer"` - Name string `json:"name" db:"name" filter:"name,string"` - DomainNames types.JSONB `json:"domain_names" db:"domain_names" filter:"domain_names,string"` - Status string `json:"status" db:"status" filter:"status,string"` - ErrorMessage string `json:"error_message" db:"error_message" filter:"error_message,string"` - Meta types.JSONB `json:"-" db:"meta"` - IsECC bool `json:"is_ecc" db:"is_ecc" filter:"is_ecc,bool"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + ExpiresOn types.NullableDBDate `json:"expires_on" gorm:"column:expires_on" filter:"expires_on,integer"` + Type string `json:"type" gorm:"column:type" filter:"type,string"` + UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` + CertificateAuthorityID uint `json:"certificate_authority_id" gorm:"column:certificate_authority_id" filter:"certificate_authority_id,integer"` + DNSProviderID uint `json:"dns_provider_id" gorm:"column:dns_provider_id" filter:"dns_provider_id,integer"` + Name string `json:"name" gorm:"column:name" filter:"name,string"` + DomainNames types.JSONB `json:"domain_names" gorm:"column:domain_names" filter:"domain_names,string"` + Status string `json:"status" gorm:"column:status" filter:"status,string"` + ErrorMessage string `json:"error_message" gorm:"column:error_message" filter:"error_message,string"` + Meta types.JSONB `json:"-" gorm:"column:meta"` + IsECC bool `json:"is_ecc" gorm:"column:is_ecc" filter:"is_ecc,bool"` // Expansions: - CertificateAuthority *certificateauthority.Model `json:"certificate_authority,omitempty"` - DNSProvider *dnsprovider.Model `json:"dns_provider,omitempty"` - User *user.Model `json:"user,omitempty"` + CertificateAuthority *certificateauthority.Model `json:"certificate_authority,omitempty" gorm:"-"` + DNSProvider *dnsprovider.Model `json:"dns_provider,omitempty" gorm:"-"` + User *user.Model `json:"user,omitempty" gorm:"-"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "certificate" } // LoadByID will load from an ID -func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) - params := []interface{}{id, 0} - return m.getByQuery(query, params) -} - -// Touch will update model's timestamp(s) -func (m *Model) Touch(created bool) { - var d types.DBDate - d.Time = time.Now() - if created { - m.CreatedOn = d - } - m.ModifiedOn = d +func (m *Model) LoadByID(id uint) error { + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // Save will save this model to the DB func (m *Model) Save() error { - var err error - if m.UserID == 0 { return eris.Errorf("User ID must be specified") } @@ -108,26 +93,22 @@ func (m *Model) Save() error { // ensure name is trimmed of whitespace m.Name = strings.TrimSpace(m.Name) - if m.ID == 0 { - m.ID, err = Create(m) - } else { - err = Update(m) - } - - return err + db := database.GetDB() + result := db.Save(m) + return result.Error } -// Delete will mark a certificate as deleted +// Delete will mark row as deleted func (m *Model) Delete() bool { - m.Touch(false) - m.IsDeleted = true - if err := m.Save(); err != nil { + if m.ID == 0 { + // Can't delete a new object return false } + db := database.GetDB() + result := db.Delete(m) + return result.Error == nil // todo: delete from acme.sh as well - - return true } // Validate will make sure the data given is expected. This object is a bit complicated, @@ -304,8 +285,8 @@ func (m *Model) GetTemplate() Template { return Template{ ID: m.ID, - CreatedOn: m.CreatedOn.Time.String(), - ModifiedOn: m.ModifiedOn.Time.String(), + CreatedAt: fmt.Sprintf("%d", m.CreatedAt), // todo: nice date string + UpdatedAt: fmt.Sprintf("%d", m.UpdatedAt), // todo: nice date string ExpiresOn: m.ExpiresOn.AsString(), Type: m.Type, UserID: m.UserID, diff --git a/backend/internal/entity/certificate/structs.go b/backend/internal/entity/certificate/structs.go deleted file mode 100644 index a9b9936..0000000 --- a/backend/internal/entity/certificate/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package certificate - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for users list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/entity/certificate/template.go b/backend/internal/entity/certificate/template.go index fd35814..2e50257 100644 --- a/backend/internal/entity/certificate/template.go +++ b/backend/internal/entity/certificate/template.go @@ -2,14 +2,14 @@ package certificate // Template is the model given to the template parser, converted from the Model type Template struct { - ID int - CreatedOn string - ModifiedOn string + ID uint + CreatedAt string + UpdatedAt string ExpiresOn string Type string - UserID int - CertificateAuthorityID int - DNSProviderID int + UserID uint + CertificateAuthorityID uint + DNSProviderID uint Name string DomainNames []string Status string diff --git a/backend/internal/entity/certificateauthority/filters.go b/backend/internal/entity/certificateauthority/filters.go deleted file mode 100644 index a16ba01..0000000 --- a/backend/internal/entity/certificateauthority/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package certificateauthority - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/certificateauthority/methods.go b/backend/internal/entity/certificateauthority/methods.go index 1631a6c..69e19b5 100644 --- a/backend/internal/entity/certificateauthority/methods.go +++ b/backend/internal/entity/certificateauthority/methods.go @@ -1,128 +1,41 @@ package certificateauthority import ( - "database/sql" - "fmt" - - "npm/internal/database" "npm/internal/entity" - "npm/internal/errors" - "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a row by ID -func GetByID(id int) (Model, error) { +func GetByID(id uint) (Model, error) { var m Model err := m.LoadByID(id) return m, err } -// Create will create a row from this model -func Create(ca *Model) (int, error) { - if ca.ID != 0 { - return 0, eris.New("Cannot create certificate authority when model already has an ID") - } - - ca.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( - created_on, - modified_on, - name, - acmesh_server, - ca_bundle, - max_domains, - is_wildcard_supported, - is_deleted - ) VALUES ( - :created_on, - :modified_on, - :name, - :acmesh_server, - :ca_bundle, - :max_domains, - :is_wildcard_supported, - :is_deleted - )`, ca) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - return int(last), nil -} - -// Update will Update a row from this model -func Update(ca *Model) error { - if ca.ID == 0 { - return eris.New("Cannot update certificate authority when model doesn't have an ID") - } - - ca.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - name = :name, - acmesh_server = :acmesh_server, - ca_bundle = :ca_bundle, - max_domains = :max_domains, - is_wildcard_supported = :is_wildcard_supported, - is_deleted = :is_deleted - WHERE id = :id`, ca) - - return err -} - // List will return a list of certificates -func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "name", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Error("ListCertificateAuthoritiesError", queryErr) - logger.Debug("%s -- %+v", query, params) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Error("ListCertificateAuthoritiesError", err) - logger.Debug("%s -- %+v", query, params) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, diff --git a/backend/internal/entity/certificateauthority/model.go b/backend/internal/entity/certificateauthority/model.go index ddb49ad..ecb40be 100644 --- a/backend/internal/entity/certificateauthority/model.go +++ b/backend/internal/entity/certificateauthority/model.go @@ -1,78 +1,55 @@ package certificateauthority import ( - "fmt" "os" "path/filepath" - "time" "npm/internal/database" + "npm/internal/entity" "npm/internal/errors" - "npm/internal/types" "github.com/rotisserie/eris" ) -const ( - tableName = "certificate_authority" -) - -// Model is the user model +// Model is the model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - Name string `json:"name" db:"name" filter:"name,string"` - AcmeshServer string `json:"acmesh_server" db:"acmesh_server" filter:"acmesh_server,string"` - CABundle string `json:"ca_bundle" db:"ca_bundle" filter:"ca_bundle,string"` - MaxDomains int `json:"max_domains" db:"max_domains" filter:"max_domains,integer"` - IsWildcardSupported bool `json:"is_wildcard_supported" db:"is_wildcard_supported" filter:"is_wildcard_supported,boolean"` - IsReadonly bool `json:"is_readonly" db:"is_readonly" filter:"is_readonly,boolean"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + Name string `json:"name" gorm:"column:name" filter:"name,string"` + AcmeshServer string `json:"acmesh_server" gorm:"column:acmesh_server" filter:"acmesh_server,string"` + CABundle string `json:"ca_bundle" gorm:"column:ca_bundle" filter:"ca_bundle,string"` + MaxDomains int `json:"max_domains" gorm:"column:max_domains" filter:"max_domains,integer"` + IsWildcardSupported bool `json:"is_wildcard_supported" gorm:"column:is_wildcard_supported" filter:"is_wildcard_supported,boolean"` + IsReadonly bool `json:"is_readonly" gorm:"column:is_readonly" filter:"is_readonly,boolean"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "certificate_authority" } // LoadByID will load from an ID -func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) - params := []interface{}{id, 0} - return m.getByQuery(query, params) -} - -// Touch will update model's timestamp(s) -func (m *Model) Touch(created bool) { - var d types.DBDate - d.Time = time.Now() - if created { - m.CreatedOn = d - } - m.ModifiedOn = d +func (m *Model) LoadByID(id uint) error { + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // Save will save this model to the DB func (m *Model) Save() error { - var err error - - if m.ID == 0 { - m.ID, err = Create(m) - } else { - err = Update(m) - } - - return err + db := database.GetDB() + result := db.Save(m) + return result.Error } -// Delete will mark a certificate as deleted +// Delete will mark row as deleted func (m *Model) Delete() bool { - m.Touch(false) - m.IsDeleted = true - if err := m.Save(); err != nil { + if m.ID == 0 { + // Can't delete a new object return false } - return true + db := database.GetDB() + result := db.Delete(m) + return result.Error == nil } // Check will ensure the ca bundle path exists if it's set diff --git a/backend/internal/entity/certificateauthority/structs.go b/backend/internal/entity/certificateauthority/structs.go deleted file mode 100644 index 85e3521..0000000 --- a/backend/internal/entity/certificateauthority/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package certificateauthority - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for users list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/entity/dnsprovider/filters.go b/backend/internal/entity/dnsprovider/filters.go deleted file mode 100644 index 30ab6c5..0000000 --- a/backend/internal/entity/dnsprovider/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package dnsprovider - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/dnsprovider/methods.go b/backend/internal/entity/dnsprovider/methods.go index 9549ecc..fdd3dc5 100644 --- a/backend/internal/entity/dnsprovider/methods.go +++ b/backend/internal/entity/dnsprovider/methods.go @@ -1,128 +1,41 @@ package dnsprovider import ( - "database/sql" - "fmt" - - "npm/internal/database" "npm/internal/entity" - "npm/internal/errors" - "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a row by ID -func GetByID(id int) (Model, error) { +func GetByID(id uint) (Model, error) { var m Model err := m.LoadByID(id) return m, err } -// Create will create a row from this model -func Create(provider *Model) (int, error) { - if provider.ID != 0 { - return 0, eris.New("Cannot create dns provider when model already has an ID") - } - - provider.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( - created_on, - modified_on, - user_id, - name, - acmesh_name, - dns_sleep, - meta, - is_deleted - ) VALUES ( - :created_on, - :modified_on, - :user_id, - :name, - :acmesh_name, - :dns_sleep, - :meta, - :is_deleted - )`, provider) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - return int(last), nil -} - -// Update will Update a row from this model -func Update(provider *Model) error { - if provider.ID == 0 { - return eris.New("Cannot update dns provider when model doesn't have an ID") - } - - provider.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - user_id = :user_id, - name = :name, - acmesh_name = :acmesh_name, - dns_sleep = :dns_sleep, - meta = :meta, - is_deleted = :is_deleted - WHERE id = :id`, provider) - - return err -} - // List will return a list of certificates -func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "name", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Error("ListDnsProvidersError", queryErr) - logger.Debug("%s -- %+v", query, params) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Error("ListDnsProvidersError", err) - logger.Debug("%s -- %+v", query, params) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, diff --git a/backend/internal/entity/dnsprovider/model.go b/backend/internal/entity/dnsprovider/model.go index 7a4f531..10afb72 100644 --- a/backend/internal/entity/dnsprovider/model.go +++ b/backend/internal/entity/dnsprovider/model.go @@ -2,80 +2,58 @@ package dnsprovider import ( "fmt" - "time" "npm/internal/database" "npm/internal/dnsproviders" + "npm/internal/entity" "npm/internal/logger" "npm/internal/types" "github.com/rotisserie/eris" ) -const ( - tableName = "dns_provider" -) - -// Model is the user model -// Also see: https://github.com/acmesh-official/acme.sh/wiki/dnscheck +// Model is the model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` - Name string `json:"name" db:"name" filter:"name,string"` - AcmeshName string `json:"acmesh_name" db:"acmesh_name" filter:"acmesh_name,string"` - DNSSleep int `json:"dns_sleep" db:"dns_sleep" filter:"dns_sleep,integer"` - Meta types.JSONB `json:"meta" db:"meta"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + UserID int `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` + Name string `json:"name" gorm:"column:name" filter:"name,string"` + AcmeshName string `json:"acmesh_name" gorm:"column:acmesh_name" filter:"acmesh_name,string"` + DNSSleep int `json:"dns_sleep" gorm:"column:dns_sleep" filter:"dns_sleep,integer"` + Meta types.JSONB `json:"meta" gorm:"column:meta"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "dns_provider" } // LoadByID will load from an ID -func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) - params := []interface{}{id, 0} - return m.getByQuery(query, params) -} - -// Touch will update model's timestamp(s) -func (m *Model) Touch(created bool) { - var d types.DBDate - d.Time = time.Now() - if created { - m.CreatedOn = d - } - m.ModifiedOn = d +func (m *Model) LoadByID(id uint) error { + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // Save will save this model to the DB func (m *Model) Save() error { - var err error - if m.UserID == 0 { return eris.Errorf("User ID must be specified") } - if m.ID == 0 { - m.ID, err = Create(m) - } else { - err = Update(m) - } - - return err + db := database.GetDB() + result := db.Save(m) + return result.Error } -// Delete will mark a certificate as deleted +// Delete will mark row as deleted func (m *Model) Delete() bool { - m.Touch(false) - m.IsDeleted = true - if err := m.Save(); err != nil { + if m.ID == 0 { + // Can't delete a new object return false } - return true + db := database.GetDB() + result := db.Delete(m) + return result.Error == nil } // GetAcmeShEnvVars returns the env vars required for acme.sh dns cert requests diff --git a/backend/internal/entity/dnsprovider/structs.go b/backend/internal/entity/dnsprovider/structs.go deleted file mode 100644 index 835c947..0000000 --- a/backend/internal/entity/dnsprovider/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package dnsprovider - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for the list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/entity/filters.go b/backend/internal/entity/filters.go index 9709fa1..e3a8c01 100644 --- a/backend/internal/entity/filters.go +++ b/backend/internal/entity/filters.go @@ -11,12 +11,6 @@ import ( // FilterMapFunction is a filter map function type FilterMapFunction func(value []string) []string -// FilterTagName tag name user for filter pickups -const FilterTagName = "filter" - -// DBTagName tag name user for field name pickups -const DBTagName = "db" - // GenerateSQLFromFilters will return a Query and params for use as WHERE clause in SQL queries // This will use a AND where clause approach. func GenerateSQLFromFilters(filters []model.Filter, fieldMap map[string]string, fieldMapFunctions map[string]FilterMapFunction) (string, []interface{}) { @@ -96,6 +90,7 @@ func getSQLAssignmentFromModifier(filter model.Filter, params *[]interface{}) st return clause } +/* // GetFilterMap returns the filter map func GetFilterMap(m interface{}) map[string]string { var filterMap = make(map[string]string) @@ -110,8 +105,8 @@ func GetFilterMap(m interface{}) map[string]string { field := t.Field(i) // Get the field tag value - filterTag := field.Tag.Get(FilterTagName) - dbTag := field.Tag.Get(DBTagName) + filterTag := field.Tag.Get("filter") + dbTag := field.Tag.Get("db") if filterTag != "" && dbTag != "" && dbTag != "-" && filterTag != "-" { // Filter tag can be a 2 part thing: name,type // ie: account_id,integer @@ -124,6 +119,7 @@ func GetFilterMap(m interface{}) map[string]string { return filterMap } +*/ // GetDBColumns returns the db columns func GetDBColumns(m interface{}) []string { @@ -132,7 +128,7 @@ func GetDBColumns(m interface{}) []string { for i := 0; i < t.NumField(); i++ { field := t.Field(i) - dbTag := field.Tag.Get(DBTagName) + dbTag := field.Tag.Get("db") if dbTag != "" && dbTag != "-" { columns = append(columns, dbTag) } diff --git a/backend/internal/entity/filters_schema.go b/backend/internal/entity/filters_schema.go index 9686293..facc8c8 100644 --- a/backend/internal/entity/filters_schema.go +++ b/backend/internal/entity/filters_schema.go @@ -14,7 +14,7 @@ func GetFilterSchema(m interface{}) string { for i := 0; i < t.NumField(); i++ { field := t.Field(i) - filterTag := field.Tag.Get(FilterTagName) + filterTag := field.Tag.Get("filter") if filterTag != "" && filterTag != "-" { // split out tag value "field,filtreType" diff --git a/backend/internal/entity/host/filters.go b/backend/internal/entity/host/filters.go deleted file mode 100644 index 7b02391..0000000 --- a/backend/internal/entity/host/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package host - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/host/methods.go b/backend/internal/entity/host/methods.go index 5e6ed14..59219be 100644 --- a/backend/internal/entity/host/methods.go +++ b/backend/internal/entity/host/methods.go @@ -1,181 +1,40 @@ package host import ( - "database/sql" - "fmt" - "npm/internal/database" "npm/internal/entity" - "npm/internal/errors" "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a Host by ID -func GetByID(id int) (Model, error) { +func GetByID(id uint) (Model, error) { var m Model err := m.LoadByID(id) return m, err } -// create will create a Host from this model -func create(host *Model) (int, error) { - if host.ID != 0 { - return 0, eris.New("Cannot create host when model already has an ID") - } - - host.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+tableName+` ( - created_on, - modified_on, - user_id, - type, - nginx_template_id, - listen_interface, - domain_names, - upstream_id, - proxy_scheme, - proxy_host, - proxy_port, - certificate_id, - access_list_id, - ssl_forced, - caching_enabled, - block_exploits, - allow_websocket_upgrade, - http2_support, - hsts_enabled, - hsts_subdomains, - paths, - advanced_config, - status, - error_message, - is_disabled, - is_deleted - ) VALUES ( - :created_on, - :modified_on, - :user_id, - :type, - :nginx_template_id, - :listen_interface, - :domain_names, - :upstream_id, - :proxy_scheme, - :proxy_host, - :proxy_port, - :certificate_id, - :access_list_id, - :ssl_forced, - :caching_enabled, - :block_exploits, - :allow_websocket_upgrade, - :http2_support, - :hsts_enabled, - :hsts_subdomains, - :paths, - :advanced_config, - :status, - :error_message, - :is_disabled, - :is_deleted - )`, host) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - logger.Debug("Created Host: %+v", host) - - return int(last), nil -} - -// update will Update a Host from this model -func update(host *Model) error { - if host.ID == 0 { - return eris.New("Cannot update host when model doesn't have an ID") - } - - host.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - user_id = :user_id, - type = :type, - nginx_template_id = :nginx_template_id, - listen_interface = :listen_interface, - domain_names = :domain_names, - upstream_id = :upstream_id, - proxy_scheme = :proxy_scheme, - proxy_host = :proxy_host, - proxy_port = :proxy_port, - certificate_id = :certificate_id, - access_list_id = :access_list_id, - ssl_forced = :ssl_forced, - caching_enabled = :caching_enabled, - block_exploits = :block_exploits, - allow_websocket_upgrade = :allow_websocket_upgrade, - http2_support = :http2_support, - hsts_enabled = :hsts_enabled, - hsts_subdomains = :hsts_subdomains, - paths = :paths, - advanced_config = :advanced_config, - status = :status, - error_message = :error_message, - is_disabled = :is_disabled, - is_deleted = :is_deleted - WHERE id = :id`, host) - - logger.Debug("Updated Host: %+v", host) - - return err -} - // List will return a list of hosts -func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "domain_names", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Debug("%s -- %+v", query, params) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Debug("%s -- %+v", query, params) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } if expand != nil { @@ -187,7 +46,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis } } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, @@ -201,32 +60,28 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis // GetUpstreamUseCount returns the number of hosts that are using // an upstream, and have not been deleted. -func GetUpstreamUseCount(upstreamID int) int { - db := database.GetInstance() - query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE upstream_id = ? AND is_deleted = ?", tableName) - countRow := db.QueryRowx(query, upstreamID, 0) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Debug("%s", query) +func GetUpstreamUseCount(upstreamID uint) int64 { + db := database.GetDB() + + var count int64 + if result := db.Model(&Model{}).Where("upstream_id = ?", upstreamID).Count(&count); result.Error != nil { + logger.Debug("GetUpstreamUseCount Error: %v", result.Error) return 0 } - return totalRows + return count } // GetCertificateUseCount returns the number of hosts that are using // a certificate, and have not been deleted. -func GetCertificateUseCount(certificateID int) int { - db := database.GetInstance() - query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE certificate_id = ? AND is_deleted = ?", tableName) - countRow := db.QueryRowx(query, certificateID, 0) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Debug("%s", query) +func GetCertificateUseCount(certificateID uint) int64 { + db := database.GetDB() + + var count int64 + if result := db.Model(&Model{}).Where("certificate_id = ?", certificateID).Count(&count); result.Error != nil { + logger.Debug("GetUpstreamUseCount Error: %v", result.Error) return 0 } - return totalRows + return count } // AddPendingJobs is intended to be used at startup to add diff --git a/backend/internal/entity/host/model.go b/backend/internal/entity/host/model.go index 12b68da..b3db092 100644 --- a/backend/internal/entity/host/model.go +++ b/backend/internal/entity/host/model.go @@ -2,9 +2,8 @@ package host import ( "fmt" - "time" - "npm/internal/database" + "npm/internal/entity" "npm/internal/entity/certificate" "npm/internal/entity/nginxtemplate" "npm/internal/entity/upstream" @@ -17,8 +16,6 @@ import ( ) const ( - tableName = "host" - // ProxyHostType is self explanatory ProxyHostType = "proxy" // RedirectionHostType is self explanatory @@ -27,67 +24,53 @@ const ( DeadHostType = "dead" ) -// Model is the user model +// Model is the model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` - Type string `json:"type" db:"type" filter:"type,string"` - NginxTemplateID int `json:"nginx_template_id" db:"nginx_template_id" filter:"nginx_template_id,integer"` - ListenInterface string `json:"listen_interface" db:"listen_interface" filter:"listen_interface,string"` - DomainNames types.JSONB `json:"domain_names" db:"domain_names" filter:"domain_names,string"` - UpstreamID int `json:"upstream_id" db:"upstream_id" filter:"upstream_id,integer"` - ProxyScheme string `json:"proxy_scheme" db:"proxy_scheme" filter:"proxy_scheme,string"` - ProxyHost string `json:"proxy_host" db:"proxy_host" filter:"proxy_host,string"` - ProxyPort int `json:"proxy_port" db:"proxy_port" filter:"proxy_port,integer"` - CertificateID int `json:"certificate_id" db:"certificate_id" filter:"certificate_id,integer"` - AccessListID int `json:"access_list_id" db:"access_list_id" filter:"access_list_id,integer"` - SSLForced bool `json:"ssl_forced" db:"ssl_forced" filter:"ssl_forced,boolean"` - CachingEnabled bool `json:"caching_enabled" db:"caching_enabled" filter:"caching_enabled,boolean"` - BlockExploits bool `json:"block_exploits" db:"block_exploits" filter:"block_exploits,boolean"` - AllowWebsocketUpgrade bool `json:"allow_websocket_upgrade" db:"allow_websocket_upgrade" filter:"allow_websocket_upgrade,boolean"` - HTTP2Support bool `json:"http2_support" db:"http2_support" filter:"http2_support,boolean"` - HSTSEnabled bool `json:"hsts_enabled" db:"hsts_enabled" filter:"hsts_enabled,boolean"` - HSTSSubdomains bool `json:"hsts_subdomains" db:"hsts_subdomains" filter:"hsts_subdomains,boolean"` - Paths string `json:"paths" db:"paths" filter:"paths,string"` - AdvancedConfig string `json:"advanced_config" db:"advanced_config" filter:"advanced_config,string"` - Status string `json:"status" db:"status" filter:"status,string"` - ErrorMessage string `json:"error_message" db:"error_message" filter:"error_message,string"` - IsDisabled bool `json:"is_disabled" db:"is_disabled" filter:"is_disabled,boolean"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` + Type string `json:"type" gorm:"column:type" filter:"type,string"` + NginxTemplateID uint `json:"nginx_template_id" gorm:"column:nginx_template_id" filter:"nginx_template_id,integer"` + ListenInterface string `json:"listen_interface" gorm:"column:listen_interface" filter:"listen_interface,string"` + DomainNames types.JSONB `json:"domain_names" gorm:"column:domain_names" filter:"domain_names,string"` + UpstreamID uint `json:"upstream_id" gorm:"column:upstream_id" filter:"upstream_id,integer"` + ProxyScheme string `json:"proxy_scheme" gorm:"column:proxy_scheme" filter:"proxy_scheme,string"` + ProxyHost string `json:"proxy_host" gorm:"column:proxy_host" filter:"proxy_host,string"` + ProxyPort int `json:"proxy_port" gorm:"column:proxy_port" filter:"proxy_port,integer"` + CertificateID uint `json:"certificate_id" gorm:"column:certificate_id" filter:"certificate_id,integer"` + AccessListID uint `json:"access_list_id" gorm:"column:access_list_id" filter:"access_list_id,integer"` + SSLForced bool `json:"ssl_forced" gorm:"column:ssl_forced" filter:"ssl_forced,boolean"` + CachingEnabled bool `json:"caching_enabled" gorm:"column:caching_enabled" filter:"caching_enabled,boolean"` + BlockExploits bool `json:"block_exploits" gorm:"column:block_exploits" filter:"block_exploits,boolean"` + AllowWebsocketUpgrade bool `json:"allow_websocket_upgrade" gorm:"column:allow_websocket_upgrade" filter:"allow_websocket_upgrade,boolean"` + HTTP2Support bool `json:"http2_support" gorm:"column:http2_support" filter:"http2_support,boolean"` + HSTSEnabled bool `json:"hsts_enabled" gorm:"column:hsts_enabled" filter:"hsts_enabled,boolean"` + HSTSSubdomains bool `json:"hsts_subdomains" gorm:"column:hsts_subdomains" filter:"hsts_subdomains,boolean"` + Paths string `json:"paths" gorm:"column:paths" filter:"paths,string"` + AdvancedConfig string `json:"advanced_config" gorm:"column:advanced_config" filter:"advanced_config,string"` + Status string `json:"status" gorm:"column:status" filter:"status,string"` + ErrorMessage string `json:"error_message" gorm:"column:error_message" filter:"error_message,string"` + IsDisabled bool `json:"is_disabled" gorm:"column:is_disabled" filter:"is_disabled,boolean"` // Expansions - Certificate *certificate.Model `json:"certificate,omitempty"` - NginxTemplate *nginxtemplate.Model `json:"nginx_template,omitempty"` - User *user.Model `json:"user,omitempty"` - Upstream *upstream.Model `json:"upstream,omitempty"` + Certificate *certificate.Model `json:"certificate,omitempty" gorm:"-"` + NginxTemplate *nginxtemplate.Model `json:"nginx_template,omitempty" gorm:"-"` + User *user.Model `json:"user,omitempty" gorm:"-"` + Upstream *upstream.Model `json:"upstream,omitempty" gorm:"-"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "host" } // LoadByID will load from an ID -func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) - params := []interface{}{id, 0} - return m.getByQuery(query, params) -} - -// Touch will update model's timestamp(s) -func (m *Model) Touch(created bool) { - var d types.DBDate - d.Time = time.Now() - if created { - m.CreatedOn = d - } - m.ModifiedOn = d +func (m *Model) LoadByID(id uint) error { + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // Save will save this model to the DB func (m *Model) Save(skipConfiguration bool) error { - var err error - if m.UserID == 0 { return eris.Errorf("User ID must be specified") } @@ -97,23 +80,20 @@ func (m *Model) Save(skipConfiguration bool) error { m.Status = status.StatusReady } - if m.ID == 0 { - m.ID, err = create(m) - } else { - err = update(m) - } - - return err + db := database.GetDB() + result := db.Save(m) + return result.Error } -// Delete will mark a host as deleted +// Delete will mark row as deleted func (m *Model) Delete() bool { - m.Touch(false) - m.IsDeleted = true - if err := m.Save(false); err != nil { + if m.ID == 0 { + // Can't delete a new object return false } - return true + db := database.GetDB() + result := db.Delete(m) + return result.Error == nil } // Expand will fill in more properties @@ -160,8 +140,8 @@ func (m *Model) GetTemplate() Template { t := Template{ ID: m.ID, - CreatedOn: m.CreatedOn.Time.String(), - ModifiedOn: m.ModifiedOn.Time.String(), + CreatedAt: fmt.Sprintf("%d", m.CreatedAt), // todo: format as nice string + UpdatedAt: fmt.Sprintf("%d", m.UpdatedAt), // todo: format as nice string UserID: m.UserID, Type: m.Type, NginxTemplateID: m.NginxTemplateID, diff --git a/backend/internal/entity/host/structs.go b/backend/internal/entity/host/structs.go deleted file mode 100644 index dda3d6f..0000000 --- a/backend/internal/entity/host/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package host - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for this list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/entity/host/template.go b/backend/internal/entity/host/template.go index 31e133a..640ee5d 100644 --- a/backend/internal/entity/host/template.go +++ b/backend/internal/entity/host/template.go @@ -4,20 +4,20 @@ import "npm/internal/entity/upstream" // Template is the model given to the template parser, converted from the Model type Template struct { - ID int - CreatedOn string - ModifiedOn string - UserID int + ID uint + CreatedAt string + UpdatedAt string + UserID uint Type string - NginxTemplateID int + NginxTemplateID uint ProxyScheme string ProxyHost string ProxyPort int ListenInterface string DomainNames []string - UpstreamID int - CertificateID int - AccessListID int + UpstreamID uint + CertificateID uint + AccessListID uint SSLForced bool CachingEnabled bool BlockExploits bool diff --git a/backend/internal/entity/lists.go b/backend/internal/entity/lists.go new file mode 100644 index 0000000..24cf5ea --- /dev/null +++ b/backend/internal/entity/lists.go @@ -0,0 +1,36 @@ +package entity + +import ( + "npm/internal/database" + "npm/internal/model" + + "gorm.io/gorm" +) + +// ListableModel is a interface for common use +type ListableModel interface { + TableName() string +} + +// ListResponse is the JSON response for users list +type ListResponse struct { + Total int64 `json:"total"` + Offset int `json:"offset"` + Limit int `json:"limit"` + Sort []model.Sort `json:"sort"` + Filter []model.Filter `json:"filter,omitempty"` + Items interface{} `json:"items,omitempty"` +} + +// ListQueryBuilder is used to setup queries for lists +func ListQueryBuilder( + pageInfo *model.PageInfo, + defaultSort model.Sort, + filters []model.Filter, +) *gorm.DB { + scopes := make([]func(*gorm.DB) *gorm.DB, 0) + scopes = append(scopes, ScopeOrderBy(pageInfo, defaultSort)) + scopes = append(scopes, ScopeOffsetLimit(pageInfo)) + // scopes = append(scopes, ScopeFilters(GetFilterMap(m))) + return database.GetDB().Scopes(scopes...) +} diff --git a/backend/internal/entity/lists_query.go b/backend/internal/entity/lists_query.go deleted file mode 100644 index 345f858..0000000 --- a/backend/internal/entity/lists_query.go +++ /dev/null @@ -1,80 +0,0 @@ -package entity - -import ( - "fmt" - "reflect" - "strings" - - "npm/internal/database" - "npm/internal/model" -) - -// ListQueryBuilder should be able to return the query and params to get items agnostically based -// on given params. -func ListQueryBuilder(modelExample interface{}, tableName string, pageInfo *model.PageInfo, defaultSort model.Sort, filters []model.Filter, filterMapFunctions map[string]FilterMapFunction, returnCount bool) (string, []interface{}) { - var queryStrings []string - var whereStrings []string - var params []interface{} - - if returnCount { - queryStrings = append(queryStrings, "SELECT COUNT(*)") - } else { - queryStrings = append(queryStrings, "SELECT *") - } - - // nolint: gosec - queryStrings = append(queryStrings, fmt.Sprintf("FROM `%s`", tableName)) - - // Append filters to where clause: - if filters != nil { - filterMap := GetFilterMap(modelExample) - filterQuery, filterParams := GenerateSQLFromFilters(filters, filterMap, filterMapFunctions) - whereStrings = []string{filterQuery} - params = append(params, filterParams...) - } - - // Add is deletee check if model has the field - if hasDeletedField(modelExample) { - params = append(params, 0) - whereStrings = append(whereStrings, "`is_deleted` = ?") - } - - // Append where clauses to query - if len(whereStrings) > 0 { - // nolint: gosec - queryStrings = append(queryStrings, fmt.Sprintf("WHERE %s", strings.Join(whereStrings, " AND "))) - } - - if !returnCount { - var orderBy string - columns := GetDBColumns(modelExample) - orderBy, pageInfo.Sort = database.BuildOrderBySQL(columns, &pageInfo.Sort) - - if orderBy != "" { - queryStrings = append(queryStrings, orderBy) - } else { - pageInfo.Sort = append(pageInfo.Sort, defaultSort) - queryStrings = append(queryStrings, fmt.Sprintf("ORDER BY `%v` COLLATE NOCASE %v", defaultSort.Field, defaultSort.Direction)) - } - - params = append(params, pageInfo.Offset) - params = append(params, pageInfo.Limit) - queryStrings = append(queryStrings, "LIMIT ?, ?") - } - - return strings.Join(queryStrings, " "), params -} - -func hasDeletedField(modelExample interface{}) bool { - t := reflect.TypeOf(modelExample) - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - dbTag := field.Tag.Get(DBTagName) - if dbTag == "is_deleted" { - return true - } - } - - return false -} diff --git a/backend/internal/entity/model_base.go b/backend/internal/entity/model_base.go new file mode 100644 index 0000000..f3de818 --- /dev/null +++ b/backend/internal/entity/model_base.go @@ -0,0 +1,13 @@ +package entity + +import ( + "gorm.io/plugin/soft_delete" +) + +// ModelBase include common fields for db control +type ModelBase struct { + ID uint `json:"id" gorm:"column:id;primaryKey"` + CreatedAt int64 `json:"created_at" gorm:"<-:create;autoCreateTime:milli;column:created_at"` + UpdatedAt int64 `json:"updated_at" gorm:"<-;autoUpdateTime:milli;column:updated_at"` + DeletedAt soft_delete.DeletedAt `json:"-" gorm:"column:is_deleted;softDelete:flag"` +} diff --git a/backend/internal/entity/nginxtemplate/filters.go b/backend/internal/entity/nginxtemplate/filters.go deleted file mode 100644 index 91fd52a..0000000 --- a/backend/internal/entity/nginxtemplate/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package nginxtemplate - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/nginxtemplate/methods.go b/backend/internal/entity/nginxtemplate/methods.go index 3852a03..58f48a2 100644 --- a/backend/internal/entity/nginxtemplate/methods.go +++ b/backend/internal/entity/nginxtemplate/methods.go @@ -1,123 +1,41 @@ package nginxtemplate import ( - "database/sql" - "fmt" - - "npm/internal/database" "npm/internal/entity" - "npm/internal/errors" - "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a Host by ID -func GetByID(id int) (Model, error) { +func GetByID(id uint) (Model, error) { var m Model err := m.LoadByID(id) return m, err } -// Create will create a Host from this model -func Create(host *Model) (int, error) { - if host.ID != 0 { - return 0, eris.New("Cannot create host template when model already has an ID") - } - - host.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( - created_on, - modified_on, - user_id, - name, - host_type, - template, - is_deleted - ) VALUES ( - :created_on, - :modified_on, - :user_id, - :name, - :host_type, - :template, - :is_deleted - )`, host) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - return int(last), nil -} - -// Update will Update a Host from this model -func Update(host *Model) error { - if host.ID == 0 { - return eris.New("Cannot update host template when model doesn't have an ID") - } - - host.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - user_id = :user_id, - name = :name, - host_type = :host_type, - template = :template, - is_deleted = :is_deleted - WHERE id = :id`, host) - - return err -} - // List will return a list of hosts -func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "created_on", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Debug("%s -- %+v", query, params) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Debug("%s -- %+v", query, params) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, diff --git a/backend/internal/entity/nginxtemplate/model.go b/backend/internal/entity/nginxtemplate/model.go index af3a4d2..1895334 100644 --- a/backend/internal/entity/nginxtemplate/model.go +++ b/backend/internal/entity/nginxtemplate/model.go @@ -1,75 +1,51 @@ package nginxtemplate import ( - "fmt" - "time" - "npm/internal/database" - "npm/internal/types" + "npm/internal/entity" "github.com/rotisserie/eris" ) -const ( - tableName = "nginx_template" -) - -// Model is the user model +// Model is the model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` - Name string `json:"name" db:"name" filter:"name,string"` - Type string `json:"type" db:"type" filter:"type,string"` - Template string `json:"template" db:"template" filter:"template,string"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + UserID int `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` + Name string `json:"name" gorm:"column:name" filter:"name,string"` + Type string `json:"type" gorm:"column:type" filter:"type,string"` + Template string `json:"template" gorm:"column:template" filter:"template,string"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "nginx_template" } // LoadByID will load from an ID -func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) - params := []interface{}{id, 0} - return m.getByQuery(query, params) -} - -// Touch will update model's timestamp(s) -func (m *Model) Touch(created bool) { - var d types.DBDate - d.Time = time.Now() - if created { - m.CreatedOn = d - } - m.ModifiedOn = d +func (m *Model) LoadByID(id uint) error { + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // Save will save this model to the DB func (m *Model) Save() error { - var err error - if m.UserID == 0 { return eris.Errorf("User ID must be specified") } - if m.ID == 0 { - m.ID, err = Create(m) - } else { - err = Update(m) - } - - return err + db := database.GetDB() + result := db.Save(m) + return result.Error } -// Delete will mark a template as deleted +// Delete will mark row as deleted func (m *Model) Delete() bool { - m.Touch(false) - m.IsDeleted = true - if err := m.Save(); err != nil { + if m.ID == 0 { + // Can't delete a new object return false } - return true + db := database.GetDB() + result := db.Delete(m) + return result.Error == nil } diff --git a/backend/internal/entity/nginxtemplate/structs.go b/backend/internal/entity/nginxtemplate/structs.go deleted file mode 100644 index 09999d1..0000000 --- a/backend/internal/entity/nginxtemplate/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package nginxtemplate - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for this list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/entity/scopes.go b/backend/internal/entity/scopes.go new file mode 100644 index 0000000..4f5e00a --- /dev/null +++ b/backend/internal/entity/scopes.go @@ -0,0 +1,62 @@ +package entity + +import ( + "strings" + + "npm/internal/model" + + "gorm.io/gorm" +) + +func ScopeOffsetLimit(pageInfo *model.PageInfo) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if pageInfo.Offset > 0 || pageInfo.Limit > 0 { + return db.Limit(pageInfo.Limit).Offset(pageInfo.Offset) + } + return db + } +} + +func ScopeOrderBy(pageInfo *model.PageInfo, defaultSort model.Sort) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if pageInfo.Sort != nil { + // Sort by items in slice + return db.Order(sortToOrderString(pageInfo.Sort)) + } else if defaultSort.Field != "" { + // Default to this sort + str := defaultSort.Field + if defaultSort.Direction != "" { + str = str + " " + defaultSort.Direction + } + return db.Order(str) + } + return db + } +} + +func ScopeFilters(filters map[string]string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + // todo + /* + if filters != nil { + filterMap := GetFilterMap(m) + filterQuery, filterParams := GenerateSQLFromFilters(filters, filterMap, filterMapFunctions) + whereStrings = []string{filterQuery} + params = append(params, filterParams...) + } + */ + return db + } +} + +func sortToOrderString(sorts []model.Sort) string { + strs := make([]string, 0) + for _, i := range sorts { + str := i.Field + if i.Direction != "" { + str = str + " " + i.Direction + } + strs = append(strs, str) + } + return strings.Join(strs, ", ") +} diff --git a/backend/internal/entity/setting/apply.go b/backend/internal/entity/setting/apply.go deleted file mode 100644 index 2942897..0000000 --- a/backend/internal/entity/setting/apply.go +++ /dev/null @@ -1,19 +0,0 @@ -package setting - -import ( - "npm/internal/config" - "npm/internal/logger" -) - -// ApplySettings will load settings from the DB and apply them where required -func ApplySettings() { - logger.Debug("Applying Settings") - - // Error-reporting - m, err := GetByName("error-reporting") - if err != nil { - logger.Error("ApplySettingsError", err) - } else { - config.ErrorReporting = m.Value.Decoded.(bool) - } -} diff --git a/backend/internal/entity/setting/filters.go b/backend/internal/entity/setting/filters.go deleted file mode 100644 index c9e9241..0000000 --- a/backend/internal/entity/setting/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package setting - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/setting/methods.go b/backend/internal/entity/setting/methods.go index f6ac0f4..2c541d4 100644 --- a/backend/internal/entity/setting/methods.go +++ b/backend/internal/entity/setting/methods.go @@ -1,16 +1,10 @@ package setting import ( - "database/sql" - "fmt" - - "npm/internal/database" + "npm/internal/config" "npm/internal/entity" - "npm/internal/errors" "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a setting by ID @@ -27,95 +21,30 @@ func GetByName(name string) (Model, error) { return m, err } -// Create will Create a Setting from this model -func Create(setting *Model) (int, error) { - if setting.ID != 0 { - return 0, eris.New("Cannot create setting when model already has an ID") - } - - setting.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( - created_on, - modified_on, - name, - value - ) VALUES ( - :created_on, - :modified_on, - :name, - :value - )`, setting) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - return int(last), nil -} - -// Update will Update a Setting from this model -func Update(setting *Model) error { - if setting.ID == 0 { - return eris.New("Cannot update setting when model doesn't have an ID") - } - - setting.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - name = :name, - value = :value - WHERE id = :id`, setting) - - return err -} - // List will return a list of settings -func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "name", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Debug("%+v", queryErr) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Debug("%+v", err) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, @@ -126,3 +55,16 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) return result, nil } + +// ApplySettings will load settings from the DB and apply them where required +func ApplySettings() { + logger.Debug("Applying Settings") + + // Error-reporting + m, err := GetByName("error-reporting") + if err != nil { + logger.Error("ApplySettingsError", err) + } else { + config.ErrorReporting = m.Value.String() == "true" + } +} diff --git a/backend/internal/entity/setting/model.go b/backend/internal/entity/setting/model.go index 05e9061..e6141ca 100644 --- a/backend/internal/entity/setting/model.go +++ b/backend/internal/entity/setting/model.go @@ -1,73 +1,50 @@ package setting import ( - "fmt" "strings" - "time" "npm/internal/database" - "npm/internal/types" + "npm/internal/entity" + + "gorm.io/datatypes" ) -const ( - tableName = "setting" -) - -// Model is the user model +// Model is the model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - Name string `json:"name" db:"name" filter:"name,string"` - Description string `json:"description" db:"description" filter:"description,string"` - Value types.JSONB `json:"value" db:"value"` + entity.ModelBase + Name string `json:"name" gorm:"column:name" filter:"name,string"` + Description string `json:"description" gorm:"column:description" filter:"description,string"` + Value datatypes.JSON `json:"value" gorm:"column:value"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "setting" } // LoadByID will load from an ID func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE `id` = ? LIMIT 1", tableName) - params := []interface{}{id} - return m.getByQuery(query, params) + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // LoadByName will load from a Name func (m *Model) LoadByName(name string) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE LOWER(`name`) = ? LIMIT 1", tableName) - params := []interface{}{strings.TrimSpace(strings.ToLower(name))} - return m.getByQuery(query, params) -} - -// Touch will update model's timestamp(s) -func (m *Model) Touch(created bool) { - var d types.DBDate - d.Time = time.Now() - if created { - m.CreatedOn = d - } - m.ModifiedOn = d + db := database.GetDB() + result := db.Where("name = ?", strings.ToLower(name)).First(&m) + return result.Error } // Save will save this model to the DB func (m *Model) Save() error { - var err error - // ensure name is trimmed of whitespace m.Name = strings.TrimSpace(m.Name) - if m.ID == 0 { - m.ID, err = Create(m) - } else { - err = Update(m) + db := database.GetDB() + if result := db.Save(m); result.Error != nil { + return result.Error } - - // Reapply settings - if err == nil { - ApplySettings() - } - - return err + ApplySettings() + return nil } diff --git a/backend/internal/entity/setting/structs.go b/backend/internal/entity/setting/structs.go deleted file mode 100644 index ba9851b..0000000 --- a/backend/internal/entity/setting/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package setting - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for settings list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/entity/stream/filters.go b/backend/internal/entity/stream/filters.go deleted file mode 100644 index bf4c883..0000000 --- a/backend/internal/entity/stream/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package stream - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/stream/methods.go b/backend/internal/entity/stream/methods.go index 446bf15..b153bd1 100644 --- a/backend/internal/entity/stream/methods.go +++ b/backend/internal/entity/stream/methods.go @@ -1,129 +1,41 @@ package stream import ( - "database/sql" - "fmt" - - "npm/internal/database" "npm/internal/entity" - "npm/internal/errors" - "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a auth by ID -func GetByID(id int) (Model, error) { +func GetByID(id uint) (Model, error) { var m Model err := m.LoadByID(id) return m, err } -// Create will create a Auth from this model -func Create(host *Model) (int, error) { - if host.ID != 0 { - return 0, eris.New("Cannot create stream when model already has an ID") - } - - host.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( - created_on, - modified_on, - user_id, - provider, - name, - domain_names, - expires_on, - meta, - is_deleted - ) VALUES ( - :created_on, - :modified_on, - :user_id, - :provider, - :name, - :domain_names, - :expires_on, - :meta, - :is_deleted - )`, host) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - return int(last), nil -} - -// Update will Update a Host from this model -func Update(host *Model) error { - if host.ID == 0 { - return eris.New("Cannot update stream when model doesn't have an ID") - } - - host.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - user_id = :user_id, - provider = :provider, - name = :name, - domain_names = :domain_names, - expires_on = :expires_on, - meta = :meta, - is_deleted = :is_deleted - WHERE id = :id`, host) - - return err -} - // List will return a list of hosts -func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "name", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Debug("%s -- %+v", query, params) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Debug("%s -- %+v", query, params) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, diff --git a/backend/internal/entity/stream/model.go b/backend/internal/entity/stream/model.go index ad158d7..e5ce115 100644 --- a/backend/internal/entity/stream/model.go +++ b/backend/internal/entity/stream/model.go @@ -1,77 +1,54 @@ package stream import ( - "fmt" - "time" - "npm/internal/database" + "npm/internal/entity" "npm/internal/types" "github.com/rotisserie/eris" ) -const ( - tableName = "stream" -) - -// Model is the user model +// Model is the model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - ExpiresOn types.DBDate `json:"expires_on" db:"expires_on" filter:"expires_on,integer"` - UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` - Provider string `json:"provider" db:"provider" filter:"provider,string"` - Name string `json:"name" db:"name" filter:"name,string"` - DomainNames types.JSONB `json:"domain_names" db:"domain_names" filter:"domain_names,string"` - Meta types.JSONB `json:"-" db:"meta"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + ExpiresOn types.DBDate `json:"expires_on" gorm:"column:expires_on" filter:"expires_on,integer"` + UserID int `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` + Provider string `json:"provider" gorm:"column:provider" filter:"provider,string"` + Name string `json:"name" gorm:"column:name" filter:"name,string"` + DomainNames types.JSONB `json:"domain_names" gorm:"column:domain_names" filter:"domain_names,string"` + Meta types.JSONB `json:"-" gorm:"column:meta"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "stream" } // LoadByID will load from an ID -func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) - params := []interface{}{id, 0} - return m.getByQuery(query, params) -} - -// Touch will update model's timestamp(s) -func (m *Model) Touch(created bool) { - var d types.DBDate - d.Time = time.Now() - if created { - m.CreatedOn = d - } - m.ModifiedOn = d +func (m *Model) LoadByID(id uint) error { + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // Save will save this model to the DB func (m *Model) Save() error { - var err error - if m.UserID == 0 { return eris.Errorf("User ID must be specified") } - if m.ID == 0 { - m.ID, err = Create(m) - } else { - err = Update(m) - } - - return err + db := database.GetDB() + result := db.Save(m) + return result.Error } -// Delete will mark a host as deleted +// Delete will mark row as deleted func (m *Model) Delete() bool { - m.Touch(false) - m.IsDeleted = true - if err := m.Save(); err != nil { + if m.ID == 0 { + // Can't delete a new object return false } - return true + db := database.GetDB() + result := db.Delete(m) + return result.Error == nil } diff --git a/backend/internal/entity/stream/structs.go b/backend/internal/entity/stream/structs.go deleted file mode 100644 index ec732c1..0000000 --- a/backend/internal/entity/stream/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package stream - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for this list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/entity/upstream/filters.go b/backend/internal/entity/upstream/filters.go deleted file mode 100644 index 82ace11..0000000 --- a/backend/internal/entity/upstream/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package upstream - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/upstream/methods.go b/backend/internal/entity/upstream/methods.go index 547fb77..13870fc 100644 --- a/backend/internal/entity/upstream/methods.go +++ b/backend/internal/entity/upstream/methods.go @@ -1,147 +1,38 @@ package upstream import ( - "database/sql" - "fmt" - - "npm/internal/database" "npm/internal/entity" - "npm/internal/errors" - "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a Upstream by ID -func GetByID(id int) (Model, error) { +func GetByID(id uint) (Model, error) { var m Model err := m.LoadByID(id) return m, err } -// create will create a Upstream from this model -func create(u *Model) (int, error) { - if u.ID != 0 { - return 0, eris.New("Cannot create upstream when model already has an ID") - } - - u.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( - created_on, - modified_on, - user_id, - name, - nginx_template_id, - ip_hash, - ntlm, - keepalive, - keepalive_requests, - keepalive_time, - keepalive_timeout, - advanced_config, - status, - error_message, - is_deleted - ) VALUES ( - :created_on, - :modified_on, - :user_id, - :name, - :nginx_template_id, - :ip_hash, - :ntlm, - :keepalive, - :keepalive_requests, - :keepalive_time, - :keepalive_timeout, - :advanced_config, - :status, - :error_message, - :is_deleted - )`, u) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - logger.Debug("Created Upstream: %+v", u) - - return int(last), nil -} - -// update will Update a Upstream from this model -func update(u *Model) error { - if u.ID == 0 { - return eris.New("Cannot update upstream when model doesn't have an ID") - } - - u.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - user_id = :user_id, - name = :name, - nginx_template_id = :nginx_template_id, - ip_hash = :ip_hash, - ntlm = :ntlm, - keepalive = :keepalive, - keepalive_requests = :keepalive_requests, - keepalive_time = :keepalive_time, - advanced_config = :advanced_config, - status = :status, - error_message = :error_message, - is_deleted = :is_deleted - WHERE id = :id`, u) - - logger.Debug("Updated Upstream: %+v", u) - - return err -} - // List will return a list of Upstreams -func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "name", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Debug("%s -- %+v", query, params) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Debug("%s -- %+v", query, params) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } // Expand to get servers, at a minimum @@ -150,7 +41,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis items[idx].Expand(expand) } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, diff --git a/backend/internal/entity/upstream/model.go b/backend/internal/entity/upstream/model.go index e42aebd..7111770 100644 --- a/backend/internal/entity/upstream/model.go +++ b/backend/internal/entity/upstream/model.go @@ -1,79 +1,55 @@ package upstream import ( - "fmt" "strings" - "time" "npm/internal/database" + "npm/internal/entity" "npm/internal/entity/nginxtemplate" "npm/internal/entity/upstreamserver" "npm/internal/entity/user" "npm/internal/status" - "npm/internal/types" "npm/internal/util" "github.com/rotisserie/eris" ) -const ( - tableName = "upstream" -) - -// Model is the Upstream model +// Model is the model // See: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` - Name string `json:"name" db:"name" filter:"name,string"` - NginxTemplateID int `json:"nginx_template_id" db:"nginx_template_id" filter:"nginx_template_id,integer"` - IPHash bool `json:"ip_hash" db:"ip_hash" filter:"ip_hash,boolean"` - NTLM bool `json:"ntlm" db:"ntlm" filter:"ntlm,boolean"` - Keepalive int `json:"keepalive" db:"keepalive" filter:"keepalive,integer"` - KeepaliveRequests int `json:"keepalive_requests" db:"keepalive_requests" filter:"keepalive_requests,integer"` - KeepaliveTime string `json:"keepalive_time" db:"keepalive_time" filter:"keepalive_time,string"` - KeepaliveTimeout string `json:"keepalive_timeout" db:"keepalive_timeout" filter:"keepalive_timeout,string"` - AdvancedConfig string `json:"advanced_config" db:"advanced_config" filter:"advanced_config,string"` - Status string `json:"status" db:"status" filter:"status,string"` - ErrorMessage string `json:"error_message" db:"error_message" filter:"error_message,string"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` + Name string `json:"name" gorm:"column:name" filter:"name,string"` + NginxTemplateID uint `json:"nginx_template_id" gorm:"column:nginx_template_id" filter:"nginx_template_id,integer"` + IPHash bool `json:"ip_hash" gorm:"column:ip_hash" filter:"ip_hash,boolean"` + NTLM bool `json:"ntlm" gorm:"column:ntlm" filter:"ntlm,boolean"` + Keepalive int `json:"keepalive" gorm:"column:keepalive" filter:"keepalive,integer"` + KeepaliveRequests int `json:"keepalive_requests" gorm:"column:keepalive_requests" filter:"keepalive_requests,integer"` + KeepaliveTime string `json:"keepalive_time" gorm:"column:keepalive_time" filter:"keepalive_time,string"` + KeepaliveTimeout string `json:"keepalive_timeout" gorm:"column:keepalive_timeout" filter:"keepalive_timeout,string"` + AdvancedConfig string `json:"advanced_config" gorm:"column:advanced_config" filter:"advanced_config,string"` + Status string `json:"status" gorm:"column:status" filter:"status,string"` + ErrorMessage string `json:"error_message" gorm:"column:error_message" filter:"error_message,string"` // Expansions - Servers []upstreamserver.Model `json:"servers"` - NginxTemplate *nginxtemplate.Model `json:"nginx_template,omitempty"` - User *user.Model `json:"user,omitempty"` + Servers []upstreamserver.Model `json:"servers" gorm:"-"` + NginxTemplate *nginxtemplate.Model `json:"nginx_template,omitempty" gorm:"-"` + User *user.Model `json:"user,omitempty" gorm:"-"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "upstream" } // LoadByID will load from an ID -func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) - params := []interface{}{id, 0} - err := m.getByQuery(query, params) - if err == nil { - err = m.Expand(nil) - } - return err -} - -// Touch will update model's timestamp(s) -func (m *Model) Touch(created bool) { - var d types.DBDate - d.Time = time.Now() - if created { - m.CreatedOn = d - } - m.ModifiedOn = d +func (m *Model) LoadByID(id uint) error { + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // Save will save this model to the DB func (m *Model) Save(skipConfiguration bool) error { - var err error - if m.UserID == 0 { return eris.Errorf("User ID must be specified") } @@ -86,34 +62,33 @@ func (m *Model) Save(skipConfiguration bool) error { m.Status = status.StatusReady } - if m.ID == 0 { - m.ID, err = create(m) - } else { - err = update(m) + db := database.GetDB() + if result := db.Save(m); result.Error != nil { + return result.Error } // Save Servers - if err == nil { - for idx := range m.Servers { - // Continue if previous iteration didn't cause an error - if err == nil { - m.Servers[idx].UpstreamID = m.ID - err = m.Servers[idx].Save() - } + var err error + for idx := range m.Servers { + // Continue if previous iteration didn't cause an error + if err == nil { + m.Servers[idx].UpstreamID = m.ID + err = m.Servers[idx].Save() } } return err } -// Delete will mark a upstream as deleted +// Delete will mark row as deleted func (m *Model) Delete() bool { - m.Touch(false) - m.IsDeleted = true - if err := m.Save(false); err != nil { + if m.ID == 0 { + // Can't delete a new object return false } - return true + db := database.GetDB() + result := db.Delete(m) + return result.Error == nil } // Expand will fill in more properties diff --git a/backend/internal/entity/upstream/structs.go b/backend/internal/entity/upstream/structs.go deleted file mode 100644 index b56dd06..0000000 --- a/backend/internal/entity/upstream/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package upstream - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for this list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/entity/upstreamserver/filters.go b/backend/internal/entity/upstreamserver/filters.go deleted file mode 100644 index e61197e..0000000 --- a/backend/internal/entity/upstreamserver/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package upstreamserver - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/upstreamserver/methods.go b/backend/internal/entity/upstreamserver/methods.go index 6358685..776ab5c 100644 --- a/backend/internal/entity/upstreamserver/methods.go +++ b/backend/internal/entity/upstreamserver/methods.go @@ -1,16 +1,9 @@ package upstreamserver import ( - "database/sql" - "fmt" - "npm/internal/database" "npm/internal/entity" - "npm/internal/errors" - "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a Upstream Server by ID @@ -21,128 +14,37 @@ func GetByID(id int) (Model, error) { } // GetByUpstreamID finds all servers in the upstream -func GetByUpstreamID(upstreamID int) ([]Model, error) { +func GetByUpstreamID(upstreamID uint) ([]Model, error) { items := make([]Model, 0) - query := `SELECT * FROM ` + fmt.Sprintf("`%s`", tableName) + ` WHERE upstream_id = ? ORDER BY server` - db := database.GetInstance() - err := db.Select(&items, query, upstreamID) - if err != nil { - logger.Debug("%s -- %d", query, upstreamID) - } - return items, err -} - -// create will create a Upstream Server from this model -func create(u *Model) (int, error) { - if u.ID != 0 { - return 0, eris.New("Cannot create upstream server when model already has an ID") - } - - u.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( - created_on, - modified_on, - upstream_id, - server, - weight, - max_conns, - max_fails, - fail_timeout, - backup, - is_deleted - ) VALUES ( - :created_on, - :modified_on, - :upstream_id, - :server, - :weight, - :max_conns, - :max_fails, - :fail_timeout, - :backup, - :is_deleted - )`, u) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - logger.Debug("Created Upstream Server: %+v", u) - - return int(last), nil -} - -// update will Update a Upstream from this model -func update(u *Model) error { - if u.ID == 0 { - return eris.New("Cannot update upstream server when model doesn't have an ID") - } - - u.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - upstream_id = :upstream_id, - server = :server, - weight = :weight, - max_conns = :max_conns, - max_fails = :max_fails, - fail_timeout = :fail_timeout, - backup = :backup, - is_deleted = :is_deleted - WHERE id = :id`, u) - - logger.Debug("Updated Upstream Server: %+v", u) - - return err + db := database.GetDB() + result := db.Where("upstream_id = ?", upstreamID).Order("server ASC").Find(&items) + return items, result.Error } // List will return a list of Upstreams -func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "server", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Debug("%s -- %+v", query, params) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Debug("%s -- %+v", query, params) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, diff --git a/backend/internal/entity/upstreamserver/model.go b/backend/internal/entity/upstreamserver/model.go index 82eaef5..9adc7cf 100644 --- a/backend/internal/entity/upstreamserver/model.go +++ b/backend/internal/entity/upstreamserver/model.go @@ -1,78 +1,48 @@ package upstreamserver import ( - "fmt" - "time" - "npm/internal/database" - "npm/internal/types" - - "github.com/rotisserie/eris" + "npm/internal/entity" ) -const ( - tableName = "upstream_server" -) - -// Model is the upstream model +// Model is the model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - UpstreamID int `json:"upstream_id" db:"upstream_id" filter:"upstream_id,integer"` - Server string `json:"server" db:"server" filter:"server,string"` - Weight int `json:"weight" db:"weight" filter:"weight,integer"` - MaxConns int `json:"max_conns" db:"max_conns" filter:"max_conns,integer"` - MaxFails int `json:"max_fails" db:"max_fails" filter:"max_fails,integer"` - FailTimeout int `json:"fail_timeout" db:"fail_timeout" filter:"fail_timeout,integer"` - Backup bool `json:"backup" db:"backup" filter:"backup,boolean"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + UpstreamID uint `json:"upstream_id" gorm:"column:upstream_id" filter:"upstream_id,integer"` + Server string `json:"server" gorm:"column:server" filter:"server,string"` + Weight int `json:"weight" gorm:"column:weight" filter:"weight,integer"` + MaxConns int `json:"max_conns" gorm:"column:max_conns" filter:"max_conns,integer"` + MaxFails int `json:"max_fails" gorm:"column:max_fails" filter:"max_fails,integer"` + FailTimeout int `json:"fail_timeout" gorm:"column:fail_timeout" filter:"fail_timeout,integer"` + Backup bool `json:"backup" gorm:"column:is_backup" filter:"backup,boolean"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - return database.GetByQuery(m, query, params) +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "upstream_server" } // LoadByID will load from an ID func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) - params := []interface{}{id, 0} - return m.getByQuery(query, params) -} - -// Touch will update model's timestamp(s) -func (m *Model) Touch(created bool) { - var d types.DBDate - d.Time = time.Now() - if created { - m.CreatedOn = d - } - m.ModifiedOn = d + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // Save will save this model to the DB func (m *Model) Save() error { - var err error - - if m.UpstreamID == 0 { - return eris.Errorf("Upstream ID must be specified") - } - - if m.ID == 0 { - m.ID, err = create(m) - } else { - err = update(m) - } - - return err + db := database.GetDB() + result := db.Save(m) + return result.Error } -// Delete will mark a upstream as deleted +// Delete will mark row as deleted func (m *Model) Delete() bool { - m.Touch(false) - m.IsDeleted = true - if err := m.Save(); err != nil { + if m.ID == 0 { + // Can't delete a new object return false } - return true + db := database.GetDB() + result := db.Delete(m) + return result.Error == nil } diff --git a/backend/internal/entity/upstreamserver/structs.go b/backend/internal/entity/upstreamserver/structs.go deleted file mode 100644 index df6a40e..0000000 --- a/backend/internal/entity/upstreamserver/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package upstreamserver - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for this list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/entity/user/filters.go b/backend/internal/entity/user/filters.go deleted file mode 100644 index bc38ef6..0000000 --- a/backend/internal/entity/user/filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package user - -import ( - "npm/internal/entity" -) - -var filterMapFunctions = make(map[string]entity.FilterMapFunction) - -// getFilterMapFunctions is a map of functions that should be executed -// during the filtering process, if a field is defined here then the value in -// the filter will be given to the defined function and it will return a new -// value for use in the sql query. -func getFilterMapFunctions() map[string]entity.FilterMapFunction { - // if len(filterMapFunctions) == 0 { - // TODO: See internal/model/file_item.go:620 for an example - // } - - return filterMapFunctions -} - -// GetFilterSchema returns filter schema -func GetFilterSchema() string { - var m Model - return entity.GetFilterSchema(m) -} diff --git a/backend/internal/entity/user/methods.go b/backend/internal/entity/user/methods.go index 5d6f329..9c1e6d7 100644 --- a/backend/internal/entity/user/methods.go +++ b/backend/internal/entity/user/methods.go @@ -1,20 +1,14 @@ package user import ( - "database/sql" - "fmt" - "npm/internal/database" "npm/internal/entity" - "npm/internal/errors" "npm/internal/logger" "npm/internal/model" - - "github.com/rotisserie/eris" ) // GetByID finds a user by ID -func GetByID(id int) (Model, error) { +func GetByID(id uint) (Model, error) { var m Model err := m.LoadByID(id) return m, err @@ -27,140 +21,38 @@ func GetByEmail(email string) (Model, error) { return m, err } -// Create will create a User from given model -func Create(user *Model) (int, error) { - // We need to ensure that a user can't be created with the same email - // as an existing non-deleted user. Usually you would do this with the - // database schema, but it's a bit more complex because of the is_deleted field. - - if user.ID != 0 { - return 0, eris.New("Cannot create user when model already has an ID") - } - - // Check if an existing user with this email exists - _, err := GetByEmail(user.Email) - if err == nil { - return 0, errors.ErrDuplicateEmailUser - } - - user.Touch(true) - - db := database.GetInstance() - // nolint: gosec - result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( - created_on, - modified_on, - name, - nickname, - email, - is_disabled - ) VALUES ( - :created_on, - :modified_on, - :name, - :nickname, - :email, - :is_disabled - )`, user) - - if err != nil { - return 0, err - } - - last, lastErr := result.LastInsertId() - if lastErr != nil { - return 0, lastErr - } - - return int(last), nil -} - -// Update will Update a User from this model -func Update(user *Model) error { - if user.ID == 0 { - return eris.New("Cannot update user when model doesn't have an ID") - } - - // Check that the email address isn't associated with another user - if existingUser, _ := GetByEmail(user.Email); existingUser.ID != 0 && existingUser.ID != user.ID { - return errors.ErrDuplicateEmailUser - } - - user.Touch(false) - - db := database.GetInstance() - // nolint: gosec - _, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET - created_on = :created_on, - modified_on = :modified_on, - name = :name, - nickname = :nickname, - email = :email, - is_disabled = :is_disabled, - is_deleted = :is_deleted - WHERE id = :id`, user) - - return err -} - // IsEnabled is used by middleware to ensure the user is still enabled // returns (userExist, isEnabled) -func IsEnabled(userID int) (bool, bool) { - // nolint: gosec - query := `SELECT is_disabled FROM ` + fmt.Sprintf("`%s`", tableName) + ` WHERE id = ? AND is_deleted = ?` - disabled := true - db := database.GetInstance() - err := db.QueryRowx(query, userID, 0).Scan(&disabled) - - if err == sql.ErrNoRows { +func IsEnabled(userID uint) (bool, bool) { + var user Model + db := database.GetDB() + if result := db.First(&user, userID); result.Error != nil { return false, false - } else if err != nil { - logger.Error("QueryError", err) } - - return true, !disabled + return true, !user.IsDisabled } // List will return a list of users -func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) { - var result ListResponse - var exampleModel Model +func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) { + var result entity.ListResponse defaultSort := model.Sort{ Field: "name", Direction: "ASC", } - db := database.GetInstance() - if db == nil { - return result, errors.ErrDatabaseUnavailable - } - - /* - filters = append(filters, model.Filter{ - Field: "is_system", - Modifier: "equals", - Value: []string{"0"}, - }) - */ + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) // Get count of items in this search - query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) - countRow := db.QueryRowx(query, params...) - var totalRows int - queryErr := countRow.Scan(&totalRows) - if queryErr != nil && queryErr != sql.ErrNoRows { - logger.Debug("Query: %s -- %+v", query, params) - return result, queryErr + var totalRows int64 + if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil { + return result, res.Error } // Get rows items := make([]Model, 0) - query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) - err := db.Select(&items, query, params...) - if err != nil { - logger.Debug("Query: %s -- %+v", query, params) - return result, err + if res := dbo.Find(&items); res.Error != nil { + return result, res.Error } for idx := range items { @@ -176,7 +68,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis } } - result = ListResponse{ + result = entity.ListResponse{ Items: items, Total: totalRows, Limit: pageInfo.Limit, @@ -190,41 +82,21 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis // DeleteAll will do just that, and should only be used for testing purposes. func DeleteAll() error { - db := database.GetInstance() - _, err := db.Exec(fmt.Sprintf("DELETE FROM `%s`", tableName)) - return err + db := database.GetDB() + result := db.Exec("DELETE FROM users") + return result.Error } // GetCapabilities gets capabilities for a user -func GetCapabilities(userID int) ([]string, error) { - var capabilities []string - db := database.GetInstance() - if db == nil { - return []string{}, errors.ErrDatabaseUnavailable +func GetCapabilities(userID uint) ([]string, error) { + capabilities := make([]string, 0) + var hasCapabilities []UserHasCapabilityModel + db := database.GetDB() + if result := db.Where("user_id = ?", userID).Find(&hasCapabilities); result.Error != nil { + return nil, result.Error } - - query := `SELECT c.name FROM "user_has_capability" h - INNER JOIN "capability" c ON c.id = h.capability_id - WHERE h.user_id = ?` - - rows, err := db.Query(query, userID) - if err != nil && err != sql.ErrNoRows { - logger.Debug("QUERY: %v -- %v", query, userID) - return []string{}, err + for _, obj := range hasCapabilities { + capabilities = append(capabilities, obj.CapabilityName) } - - // nolint: errcheck - defer rows.Close() - - for rows.Next() { - var name string - err := rows.Scan(&name) - if err != nil { - return []string{}, err - } - - capabilities = append(capabilities, name) - } - return capabilities, nil } diff --git a/backend/internal/entity/user/model.go b/backend/internal/entity/user/model.go index e759854..418cffa 100644 --- a/backend/internal/entity/user/model.go +++ b/backend/internal/entity/user/model.go @@ -1,62 +1,67 @@ package user import ( - "fmt" "strings" - "time" "npm/internal/database" + "npm/internal/entity" "npm/internal/entity/auth" "npm/internal/errors" - "npm/internal/logger" - "npm/internal/types" "npm/internal/util" "github.com/drexedam/gravatar" "github.com/rotisserie/eris" ) -const ( - tableName = "user" -) - -// Model is the user model +// Model is the model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - Name string `json:"name" db:"name" filter:"name,string"` - Nickname string `json:"nickname" db:"nickname" filter:"nickname,string"` - Email string `json:"email" db:"email" filter:"email,email"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - GravatarURL string `json:"gravatar_url"` - IsDisabled bool `json:"is_disabled" db:"is_disabled" filter:"is_disabled,boolean"` - IsSystem bool `json:"is_system,omitempty" db:"is_system"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + entity.ModelBase + Name string `json:"name" gorm:"column:name" filter:"name,string"` + Nickname string `json:"nickname" gorm:"column:nickname" filter:"nickname,string"` + Email string `json:"email" gorm:"column:email" filter:"email,email"` + IsDisabled bool `json:"is_disabled" gorm:"column:is_disabled" filter:"is_disabled,boolean"` + IsSystem bool `json:"is_system,omitempty" gorm:"column:is_system"` + // Other + GravatarURL string `json:"gravatar_url" gorm:"-"` // Expansions - Auth *auth.Model `json:"auth,omitempty" db:"-"` - Capabilities []string `json:"capabilities,omitempty"` + Auth *auth.Model `json:"auth,omitempty" gorm:"-"` + Capabilities []string `json:"capabilities,omitempty" gorm:"-"` } -func (m *Model) getByQuery(query string, params []interface{}) error { - err := database.GetByQuery(m, query, params) - m.generateGravatar() - return err +// TableName overrides the table name used by gorm +func (Model) TableName() string { + return "user" +} + +// UserHasCapabilityModel is the model +type UserHasCapabilityModel struct { + UserID uint `json:"user_id" gorm:"column:user_id"` + CapabilityName string `json:"name" gorm:"column:capability_name"` +} + +// TableName overrides the table name used by gorm +func (UserHasCapabilityModel) TableName() string { + return "user_has_capability" } // LoadByID will load from an ID -func (m *Model) LoadByID(id int) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) - params := []interface{}{id, false} - return m.getByQuery(query, params) +func (m *Model) LoadByID(id uint) error { + db := database.GetDB() + result := db.First(&m, id) + return result.Error } // LoadByEmail will load from an Email func (m *Model) LoadByEmail(email string) error { - query := fmt.Sprintf("SELECT * FROM `%s` WHERE email = ? AND is_deleted = ? AND is_system = ? LIMIT 1", tableName) - params := []interface{}{strings.TrimSpace(strings.ToLower(email)), false, false} - return m.getByQuery(query, params) + db := database.GetDB() + result := db. + Where("email = ?", strings.TrimSpace(strings.ToLower(email))). + Where("is_system = ?", false). + First(&m) + return result.Error } +/* // Touch will update model's timestamp(s) func (m *Model) Touch(created bool) { var d types.DBDate @@ -67,34 +72,31 @@ func (m *Model) Touch(created bool) { m.ModifiedOn = d m.generateGravatar() } +*/ // Save will save this model to the DB func (m *Model) Save() error { - var err error // Ensure email is nice m.Email = strings.TrimSpace(strings.ToLower(m.Email)) - if m.IsSystem { return errors.ErrSystemUserReadonly } - if m.ID == 0 { - m.ID, err = Create(m) - } else { - err = Update(m) - } - - return err + db := database.GetDB() + // todo: touch? not sure that save does this or not? + result := db.Save(m) + return result.Error } // Delete will mark a user as deleted func (m *Model) Delete() bool { - m.Touch(false) - m.IsDeleted = true - if err := m.Save(); err != nil { + if m.ID == 0 { + // Can't delete a new object return false } - return true + db := database.GetDB() + result := db.Delete(m) + return result.Error == nil } // SetPermissions will wipe out any existing permissions and add new ones for this user @@ -103,30 +105,20 @@ func (m *Model) SetPermissions(permissions []string) error { return eris.Errorf("Cannot set permissions without first saving the User") } - db := database.GetInstance() - + db := database.GetDB() // Wipe out previous permissions - query := `DELETE FROM "user_has_capability" WHERE "user_id" = ?` - if _, err := db.Exec(query, m.ID); err != nil { - logger.Debug("QUERY: %v -- %v", query, m.ID) - return err + if result := db.Where("user_id = ?", m.ID).Delete(&UserHasCapabilityModel{}); result.Error != nil { + return result.Error } if len(permissions) > 0 { // Add new permissions + objs := []*UserHasCapabilityModel{} for _, permission := range permissions { - query = `INSERT INTO "user_has_capability" ( - "user_id", "capability_id" - ) VALUES ( - ?, - (SELECT id FROM capability WHERE name = ?) - )` - - _, err := db.Exec(query, m.ID, permission) - if err != nil { - logger.Debug("QUERY: %v -- %v -- %v", query, m.ID, permission) - return err - } + objs = append(objs, &UserHasCapabilityModel{UserID: m.ID, CapabilityName: permission}) + } + if result := db.Create(objs); result.Error != nil { + return result.Error } } @@ -164,21 +156,18 @@ func (m *Model) SaveCapabilities() error { return eris.New("At least 1 capability required for a user") } - db := database.GetInstance() - + db := database.GetDB() // Get a full list of capabilities - var capabilities []string - query := `SELECT "name" from "capability"` - err := db.Select(&capabilities, query) - if err != nil { - return err + var capabilities []entity.Capability + if result := db.Find(&capabilities); result.Error != nil { + return result.Error } // Check that the capabilities defined exist in the db for _, cap := range m.Capabilities { found := false for _, a := range capabilities { - if a == cap { + if a.Name == cap { found = true } } diff --git a/backend/internal/entity/user/structs.go b/backend/internal/entity/user/structs.go deleted file mode 100644 index f9f4490..0000000 --- a/backend/internal/entity/user/structs.go +++ /dev/null @@ -1,15 +0,0 @@ -package user - -import ( - "npm/internal/model" -) - -// ListResponse is the JSON response for users list -type ListResponse struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Sort []model.Sort `json:"sort"` - Filter []model.Filter `json:"filter,omitempty"` - Items []Model `json:"items,omitempty"` -} diff --git a/backend/internal/jwt/jwt.go b/backend/internal/jwt/jwt.go index bd45f16..acc365f 100644 --- a/backend/internal/jwt/jwt.go +++ b/backend/internal/jwt/jwt.go @@ -12,7 +12,7 @@ import ( // UserJWTClaims is the structure of a JWT for a User type UserJWTClaims struct { - UserID int `json:"uid"` + UserID uint `json:"uid"` Roles []string `json:"roles"` jwt.StandardClaims } diff --git a/backend/internal/model/pageinfo.go b/backend/internal/model/pageinfo.go index 3f1fd26..eb51d87 100644 --- a/backend/internal/model/pageinfo.go +++ b/backend/internal/model/pageinfo.go @@ -1,18 +1,12 @@ package model -import ( - "time" -) - // PageInfo is the model used by Api Handlers and passed on to other parts // of the application type PageInfo struct { - FromDate time.Time `json:"from_date"` - ToDate time.Time `json:"to_date"` - Sort []Sort `json:"sort"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Expand []string `json:"expand"` + Sort []Sort `json:"sort"` + Offset int `json:"offset"` + Limit int `json:"limit"` + Expand []string `json:"expand"` } // Sort holds the sorting data diff --git a/backend/internal/nginx/control.go b/backend/internal/nginx/control.go index 15d03af..e2f39da 100644 --- a/backend/internal/nginx/control.go +++ b/backend/internal/nginx/control.go @@ -49,9 +49,10 @@ func ConfigureHost(h host.Model) error { removeHostFiles(h) filename := getHostFilename(h, "") - if h.IsDeleted { - filename = getHostFilename(h, DeletedSuffix) - } else if h.IsDisabled { + // if h.IsDeleted { + // filename = getHostFilename(h, DeletedSuffix) + // } else if h.IsDisabled { + if h.IsDisabled { filename = getHostFilename(h, DisabledSuffix) } @@ -73,7 +74,7 @@ func ConfigureHost(h host.Model) error { // Write the .error file, if this isn't a deleted or disabled host // as the reload will only fail because of this host, if it's enabled - if !h.IsDeleted && !h.IsDisabled { + if !h.IsDisabled { filename = getHostFilename(h, ErrorSuffix) // Clear existing file(s) again removeHostFiles(h) @@ -108,9 +109,9 @@ func ConfigureUpstream(u upstream.Model) error { removeUpstreamFiles(u) filename := getUpstreamFilename(u, "") - if u.IsDeleted { - filename = getUpstreamFilename(u, DeletedSuffix) - } + // if u.IsDeleted { + // filename = getUpstreamFilename(u, DeletedSuffix) + // } // Write the config to disk err := writeTemplate(filename, u.NginxTemplate.Template, data, "") @@ -130,14 +131,14 @@ func ConfigureUpstream(u upstream.Model) error { // Write the .error file, if this isn't a deleted upstream // as the reload will only fail because of this upstream - if !u.IsDeleted { - filename = getUpstreamFilename(u, ErrorSuffix) - // Clear existing file(s) again - removeUpstreamFiles(u) - // Write the template again, but with an error message at the end of the file - // nolint: errcheck, gosec - writeTemplate(filename, u.NginxTemplate.Template, data, u.ErrorMessage) - } + // if !u.IsDeleted { + filename = getUpstreamFilename(u, ErrorSuffix) + // Clear existing file(s) again + removeUpstreamFiles(u) + // Write the template again, but with an error message at the end of the file + // nolint: errcheck, gosec + writeTemplate(filename, u.NginxTemplate.Template, data, u.ErrorMessage) + // } logger.Debug(u.ErrorMessage) } else { @@ -195,9 +196,9 @@ func GetHostConfigContent(h host.Model) (string, error) { if h.IsDisabled { filename = getHostFilename(h, DisabledSuffix) } - if h.IsDeleted { - filename = getHostFilename(h, DeletedSuffix) - } + // if h.IsDeleted { + // filename = getHostFilename(h, DeletedSuffix) + // } // nolint: gosec cnt, err := os.ReadFile(filename) @@ -213,9 +214,9 @@ func GetUpstreamConfigContent(u upstream.Model) (string, error) { if u.ErrorMessage != "" { filename = getUpstreamFilename(u, ErrorSuffix) } - if u.IsDeleted { - filename = getUpstreamFilename(u, DeletedSuffix) - } + // if u.IsDeleted { + // filename = getUpstreamFilename(u, DeletedSuffix) + // } // nolint: gosec cnt, err := os.ReadFile(filename) diff --git a/backend/internal/nginx/template_test.go b/backend/internal/nginx/template_test.go index 4fbcacc..3768709 100644 --- a/backend/internal/nginx/template_test.go +++ b/backend/internal/nginx/template_test.go @@ -3,6 +3,7 @@ package nginx import ( "testing" + "npm/internal/entity" "npm/internal/entity/certificate" "npm/internal/entity/host" @@ -45,7 +46,9 @@ server { IsDisabled: false, }, cert: certificate.Model{ - ID: 77, + ModelBase: entity.ModelBase{ + ID: 77, + }, Status: certificate.StatusProvided, Type: certificate.TypeHTTP, CertificateAuthorityID: 99, @@ -61,7 +64,9 @@ server { IsDisabled: false, }, cert: certificate.Model{ - ID: 66, + ModelBase: entity.ModelBase{ + ID: 66, + }, Status: certificate.StatusProvided, Type: certificate.TypeCustom, }, diff --git a/scripts/ci/build-backend b/scripts/ci/build-backend index 414be63..4c336fd 100755 --- a/scripts/ci/build-backend +++ b/scripts/ci/build-backend @@ -58,6 +58,7 @@ build_backend() { -w '/app/backend' \ "${IMAGE}" \ go build \ + -tags 'json1' \ -buildvcs=false \ -ldflags "-w -s -X main.commit=${BUILD_COMMIT:-notset} -X main.version=${BUILD_VERSION} -X main.sentryDSN=${SENTRY_DSN:-}" \ -o "/app/bin/$FILENAME" \ diff --git a/scripts/go-multiarch-wrapper b/scripts/go-multiarch-wrapper index d2b7883..1073cee 100755 --- a/scripts/go-multiarch-wrapper +++ b/scripts/go-multiarch-wrapper @@ -24,6 +24,7 @@ echo -e "${BLUE}❯ ${CYAN}Building binaries for ${YELLOW}${GOARCH} (${TARGETPLA # server go build \ + -tags 'json1' \ -buildvcs=false \ -ldflags "-w -s -X main.commit=${BUILD_COMMIT:-notset} -X main.version=${BUILD_VERSION} -X main.sentryDSN=${SENTRY_DSN:-}" \ -o "${1:-/dist/server}" \