Convert db backend to use Gorm, with basis for support

for Mysql and Postgres in addition to existing Sqlite
This commit is contained in:
Jamie Curnow 2023-05-26 11:04:43 +10:00
parent b4e5b8b6db
commit 29990110b1
No known key found for this signature in database
GPG Key ID: FFBB624C43388E9E
93 changed files with 1215 additions and 3075 deletions

View File

@ -30,7 +30,7 @@ tasks:
silent: true silent: true
- cmd: rm -f dist/bin/* - cmd: rm -f dist/bin/*
silent: true 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 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 - 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 silent: true

View File

@ -11,6 +11,8 @@ import (
"npm/internal/entity/certificate" "npm/internal/entity/certificate"
"npm/internal/entity/host" "npm/internal/entity/host"
"npm/internal/entity/setting" "npm/internal/entity/setting"
"npm/internal/entity/user"
"npm/internal/errors"
"npm/internal/jobqueue" "npm/internal/jobqueue"
"npm/internal/logger" "npm/internal/logger"
) )
@ -25,7 +27,7 @@ func main() {
database.Migrate(func() { database.Migrate(func() {
setting.ApplySettings() setting.ApplySettings()
database.CheckSetup() checkSetup()
// Internal Job Queue // Internal Job Queue
jobqueue.Start() jobqueue.Start()
@ -41,7 +43,8 @@ func main() {
if irq == syscall.SIGINT || irq == syscall.SIGTERM { if irq == syscall.SIGINT || irq == syscall.SIGTERM {
logger.Info("Got ", irq, " shutting server down ...") logger.Info("Got ", irq, " shutting server down ...")
// Close db // Close db
err := database.GetInstance().Close() sqlDB, _ := database.GetDB().DB()
err := sqlDB.Close()
if err != nil { if err != nil {
logger.Error("DatabaseCloseError", err) 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)
}
}

3
backend/db/schema.sql Normal file
View File

@ -0,0 +1,3 @@
INSERT INTO "schema_migrations" (version) VALUES
('20201013035318'),
('20201013035839');

View File

@ -14,7 +14,7 @@ var Assets embed.FS
// MigrationFiles are database migrations // MigrationFiles are database migrations
// //
//go:embed migrations/*.sql //go:embed migrations
var MigrationFiles embed.FS var MigrationFiles embed.FS
// NginxFiles hold nginx config templates // NginxFiles hold nginx config templates

View File

@ -3,40 +3,39 @@
CREATE TABLE IF NOT EXISTS `user` CREATE TABLE IF NOT EXISTS `user`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
name TEXT NOT NULL, name TEXT NOT NULL,
nickname TEXT NOT NULL, nickname TEXT NOT NULL,
email TEXT NOT NULL, email TEXT NOT NULL,
is_system INTEGER NOT NULL DEFAULT 0, is_system INTEGER NOT NULL DEFAULT 0,
is_disabled INTEGER NOT NULL DEFAULT 0, is_disabled INTEGER NOT NULL DEFAULT 0
is_deleted INTEGER NOT NULL DEFAULT 0
); );
CREATE TABLE IF NOT EXISTS `capability` CREATE TABLE IF NOT EXISTS `capability`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT PRIMARY KEY,
name TEXT NOT NULL,
UNIQUE (name) UNIQUE (name)
); );
CREATE TABLE IF NOT EXISTS `user_has_capability` CREATE TABLE IF NOT EXISTS `user_has_capability`
( (
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
capability_id INTEGER NOT NULL, capability_name TEXT NOT NULL,
UNIQUE (user_id, capability_id), UNIQUE (user_id, capability_name),
FOREIGN KEY (capability_id) REFERENCES capability (id) FOREIGN KEY (capability_name) REFERENCES capability (name)
); );
CREATE TABLE IF NOT EXISTS `auth` CREATE TABLE IF NOT EXISTS `auth`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
type TEXT NOT NULL, type TEXT NOT NULL,
secret TEXT NOT NULL, secret TEXT NOT NULL,
is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (user_id) REFERENCES user (id),
UNIQUE (user_id, type) UNIQUE (user_id, type)
); );
@ -44,8 +43,9 @@ CREATE TABLE IF NOT EXISTS `auth`
CREATE TABLE IF NOT EXISTS `setting` CREATE TABLE IF NOT EXISTS `setting`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
name TEXT NOT NULL, name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT "", description TEXT NOT NULL DEFAULT "",
value TEXT NOT NULL, value TEXT NOT NULL,
@ -55,8 +55,9 @@ CREATE TABLE IF NOT EXISTS `setting`
CREATE TABLE IF NOT EXISTS `audit_log` CREATE TABLE IF NOT EXISTS `audit_log`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
object_type TEXT NOT NULL, object_type TEXT NOT NULL,
object_id INTEGER 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` CREATE TABLE IF NOT EXISTS `certificate_authority`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
name TEXT NOT NULL, name TEXT NOT NULL,
acmesh_server TEXT NOT NULL DEFAULT "", acmesh_server TEXT NOT NULL DEFAULT "",
ca_bundle 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 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 max_domains INTEGER NOT NULL DEFAULT 5, -- per request
is_readonly INTEGER NOT NULL DEFAULT 0, is_readonly INTEGER NOT NULL DEFAULT 0
is_deleted INTEGER NOT NULL DEFAULT 0
); );
CREATE TABLE IF NOT EXISTS `dns_provider` CREATE TABLE IF NOT EXISTS `dns_provider`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
acmesh_name TEXT NOT NULL, acmesh_name TEXT NOT NULL,
dns_sleep INTEGER NOT NULL DEFAULT 0, dns_sleep INTEGER NOT NULL DEFAULT 0,
meta TEXT NOT NULL, meta TEXT NOT NULL,
is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id) FOREIGN KEY (user_id) REFERENCES user (id)
); );
CREATE TABLE IF NOT EXISTS `certificate` CREATE TABLE IF NOT EXISTS `certificate`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on 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 type TEXT NOT NULL, -- custom,dns,http
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
certificate_authority_id INTEGER, -- 0 for a custom cert 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 "", error_message text NOT NULL DEFAULT "",
meta TEXT NOT NULL, meta TEXT NOT NULL,
is_ecc INTEGER NOT NULL DEFAULT 0, is_ecc INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (user_id) REFERENCES user (id),
FOREIGN KEY (certificate_authority_id) REFERENCES certificate_authority (id), FOREIGN KEY (certificate_authority_id) REFERENCES certificate_authority (id),
FOREIGN KEY (dns_provider_id) REFERENCES dns_provider (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` CREATE TABLE IF NOT EXISTS `stream`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
listen_interface TEXT NOT NULL, listen_interface TEXT NOT NULL,
incoming_port INTEGER NOT NULL, incoming_port INTEGER NOT NULL,
@ -127,15 +129,15 @@ CREATE TABLE IF NOT EXISTS `stream`
udp_forwarding INTEGER NOT NULL DEFAULT 0, udp_forwarding INTEGER NOT NULL DEFAULT 0,
advanced_config TEXT NOT NULL, advanced_config TEXT NOT NULL,
is_disabled INTEGER NOT NULL DEFAULT 0, is_disabled INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id) FOREIGN KEY (user_id) REFERENCES user (id)
); );
CREATE TABLE IF NOT EXISTS `upstream` CREATE TABLE IF NOT EXISTS `upstream`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
nginx_template_id INTEGER NOT NULL, nginx_template_id INTEGER NOT NULL,
@ -148,7 +150,6 @@ CREATE TABLE IF NOT EXISTS `upstream`
advanced_config TEXT NOT NULL, advanced_config TEXT NOT NULL,
status TEXT NOT NULL DEFAULT "", status TEXT NOT NULL DEFAULT "",
error_message 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 (user_id) REFERENCES user (id),
FOREIGN KEY (nginx_template_id) REFERENCES nginx_template (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` CREATE TABLE IF NOT EXISTS `upstream_server`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
upstream_id INTEGER NOT NULL, upstream_id INTEGER NOT NULL,
server TEXT NOT NULL, server TEXT NOT NULL,
weight INTEGER NOT NULL DEFAULT 0, weight INTEGER NOT NULL DEFAULT 0,
max_conns INTEGER NOT NULL DEFAULT 0, max_conns INTEGER NOT NULL DEFAULT 0,
max_fails INTEGER NOT NULL DEFAULT 0, max_fails INTEGER NOT NULL DEFAULT 0,
fail_timeout INTEGER NOT NULL DEFAULT 0, fail_timeout INTEGER NOT NULL DEFAULT 0,
backup INTEGER NOT NULL DEFAULT 0, is_backup INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (upstream_id) REFERENCES upstream (id) FOREIGN KEY (upstream_id) REFERENCES upstream (id)
); );
CREATE TABLE IF NOT EXISTS `access_list` CREATE TABLE IF NOT EXISTS `access_list`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
meta TEXT NOT NULL, meta TEXT NOT NULL,
is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id) FOREIGN KEY (user_id) REFERENCES user (id)
); );
CREATE TABLE IF NOT EXISTS `nginx_template` CREATE TABLE IF NOT EXISTS `nginx_template`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
type TEXT NOT NULL, type TEXT NOT NULL,
template TEXT NOT NULL, template TEXT NOT NULL,
is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id) FOREIGN KEY (user_id) REFERENCES user (id)
); );
CREATE TABLE IF NOT EXISTS `host` CREATE TABLE IF NOT EXISTS `host`
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
created_on INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
type TEXT NOT NULL, type TEXT NOT NULL,
nginx_template_id INTEGER NOT NULL, nginx_template_id INTEGER NOT NULL,
@ -222,7 +224,6 @@ CREATE TABLE IF NOT EXISTS `host`
status TEXT NOT NULL DEFAULT "", status TEXT NOT NULL DEFAULT "",
error_message TEXT NOT NULL DEFAULT "", error_message TEXT NOT NULL DEFAULT "",
is_disabled INTEGER NOT NULL DEFAULT 0, is_disabled INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (user_id) REFERENCES user (id),
FOREIGN KEY (nginx_template_id) REFERENCES nginx_template (id), FOREIGN KEY (nginx_template_id) REFERENCES nginx_template (id),
FOREIGN KEY (upstream_id) REFERENCES upstream (id), FOREIGN KEY (upstream_id) REFERENCES upstream (id),

View File

@ -25,14 +25,14 @@ INSERT INTO `capability` (
-- Default error reporting setting -- Default error reporting setting
INSERT INTO `setting` ( INSERT INTO `setting` (
created_on, created_at,
modified_on, updated_at,
name, name,
description, description,
value value
) VALUES ( ) VALUES (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
"error-reporting", "error-reporting",
"If enabled, any application errors are reported to Sentry. Sensitive information is not sent.", "If enabled, any application errors are reported to Sentry. Sensitive information is not sent.",
"true" -- remember this is json "true" -- remember this is json
@ -40,14 +40,14 @@ INSERT INTO `setting` (
-- Default site -- Default site
INSERT INTO `setting` ( INSERT INTO `setting` (
created_on, created_at,
modified_on, updated_at,
name, name,
description, description,
value value
) VALUES ( ) VALUES (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
"default-site", "default-site",
"What to show users who hit your Nginx server by default", "What to show users who hit your Nginx server by default",
'"welcome"' -- remember this is json '"welcome"' -- remember this is json
@ -56,56 +56,56 @@ INSERT INTO `setting` (
-- Default Certificate Authorities -- Default Certificate Authorities
INSERT INTO `certificate_authority` ( INSERT INTO `certificate_authority` (
created_on, created_at,
modified_on, updated_at,
name, name,
acmesh_server, acmesh_server,
is_wildcard_supported, is_wildcard_supported,
max_domains, max_domains,
is_readonly is_readonly
) VALUES ( ) VALUES (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
"ZeroSSL", "ZeroSSL",
"zerossl", "zerossl",
1, 1,
10, 10,
1 1
), ( ), (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
"Let's Encrypt", "Let's Encrypt",
"https://acme-v02.api.letsencrypt.org/directory", "https://acme-v02.api.letsencrypt.org/directory",
1, 1,
10, 10,
1 1
), ( ), (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
"Buypass Go SSL", "Buypass Go SSL",
"https://api.buypass.com/acme/directory", "https://api.buypass.com/acme/directory",
0, 0,
5, 5,
1 1
), ( ), (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
"SSL.com", "SSL.com",
"ssl.com", "ssl.com",
0, 0,
10, 10,
1 1
), ( ), (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
"Let's Encrypt (Testing)", "Let's Encrypt (Testing)",
"https://acme-staging-v02.api.letsencrypt.org/directory", "https://acme-staging-v02.api.letsencrypt.org/directory",
1, 1,
10, 10,
1 1
), ( ), (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
"Buypass Go SSL (Testing)", "Buypass Go SSL (Testing)",
"https://api.test4.buypass.no/acme/directory", "https://api.test4.buypass.no/acme/directory",
0, 0,
@ -115,15 +115,15 @@ INSERT INTO `certificate_authority` (
-- System User -- System User
INSERT INTO `user` ( INSERT INTO `user` (
created_on, created_at,
modified_on, updated_at,
name, name,
nickname, nickname,
email, email,
is_system is_system
) VALUES ( ) VALUES (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
"System", "System",
"System", "System",
"system@localhost", "system@localhost",
@ -132,15 +132,15 @@ INSERT INTO `user` (
-- Host Templates -- Host Templates
INSERT INTO `nginx_template` ( INSERT INTO `nginx_template` (
created_on, created_at,
modified_on, updated_at,
user_id, user_id,
name, name,
type, type,
template template
) VALUES ( ) VALUES (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
(SELECT id FROM user WHERE is_system = 1 LIMIT 1), (SELECT id FROM user WHERE is_system = 1 LIMIT 1),
"Default Proxy Template", "Default Proxy Template",
"proxy", "proxy",
@ -262,29 +262,29 @@ server {
} }
" "
), ( ), (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
(SELECT id FROM user WHERE is_system = 1 LIMIT 1), (SELECT id FROM user WHERE is_system = 1 LIMIT 1),
"Default Redirect Template", "Default Redirect Template",
"redirect", "redirect",
"# this is a redirect template" "# this is a redirect template"
), ( ), (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
(SELECT id FROM user WHERE is_system = 1 LIMIT 1), (SELECT id FROM user WHERE is_system = 1 LIMIT 1),
"Default Dead Template", "Default Dead Template",
"dead", "dead",
"# this is a dead template" "# this is a dead template"
), ( ), (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
(SELECT id FROM user WHERE is_system = 1 LIMIT 1), (SELECT id FROM user WHERE is_system = 1 LIMIT 1),
"Default Stream Template", "Default Stream Template",
"stream", "stream",
"# this is a stream template" "# this is a stream template"
), ( ), (
strftime('%s', 'now'), unixepoch() * 1000,
strftime('%s', 'now'), unixepoch() * 1000,
(SELECT id FROM user WHERE is_system = 1 LIMIT 1), (SELECT id FROM user WHERE is_system = 1 LIMIT 1),
"Default Upstream Template", "Default Upstream Template",
"upstream", "upstream",

View File

@ -4,54 +4,61 @@ go 1.20
require ( require (
github.com/alexflint/go-arg v1.4.3 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/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/drexedam/gravatar v0.0.0-20210327211422-e94eea8c338e github.com/drexedam/gravatar v0.0.0-20210327211422-e94eea8c338e
github.com/fatih/color v1.13.0 github.com/fatih/color v1.15.0
github.com/getsentry/sentry-go v0.17.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/chi v4.1.2+incompatible
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-chi/jwtauth v4.0.4+incompatible github.com/go-chi/jwtauth v4.0.4+incompatible
github.com/jc21/go-sse v0.0.0-20230307071053-2e6b1dbcb7ec github.com/jc21/go-sse v0.0.0-20230307071053-2e6b1dbcb7ec
github.com/jc21/jsref v0.0.0-20210608024405-a97debfc4760 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/patrickmn/go-cache v2.1.0+incompatible
github.com/qri-io/jsonschema v0.2.1 github.com/qri-io/jsonschema v0.2.1
github.com/rotisserie/eris v0.5.4 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 github.com/vrischmann/envconfig v1.3.0
golang.org/x/crypto v0.5.0 golang.org/x/crypto v0.9.0
modernc.org/sqlite v1.21.1 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 ( require (
github.com/alexflint/go-scalar v1.2.0 // indirect github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // 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/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/jspointer v0.0.0-20181205001929-82fadba7561c // indirect
github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/option v1.0.1 // indirect
github.com/lestrrat-go/pdebug/v3 v3.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/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-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/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/qri-io/jsonpointer v0.1.1 // indirect github.com/qri-io/jsonpointer v0.1.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect
golang.org/x/sys v0.4.0 // indirect golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.6.0 // indirect golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.1.12 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect modernc.org/libc v1.22.6 // 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/mathutil v1.5.0 // indirect modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect
modernc.org/opt v0.1.3 // indirect modernc.org/sqlite v1.22.1 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
) )

View File

@ -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.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 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= 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 h1:Ppm0npCCsmuR9oQaBtRuZcmILVE74aXE+AmrJj8L2ns=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= 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= 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/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 h1:2R8DvYLNr5DL25eWwpOdPno1eIbTNjJC0d7v8ti5cus=
github.com/drexedam/gravatar v0.0.0-20210327211422-e94eea8c338e/go.mod h1:YjikoytuRI4q+GRd3xrOrKJN+Ayi2dwRomHLDDeMHfs= 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.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/getsentry/sentry-go v0.17.0 h1:UustVWnOoDFHBS7IJUB2QK/nB5pap748ZEp0swnQJak= github.com/getsentry/sentry-go v0.21.0 h1:c9l5F1nPF30JIppulk4veau90PK6Smu3abgVtVQWon4=
github.com/getsentry/sentry-go v0.17.0/go.mod h1:B82dxtBvxG0KaPD8/hfSV+VcHD+Lg/xUS4JuQn1P4cM= 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 h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 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= 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 h1:LGIxg6YfvSBzxU2BljXbrzVc1fMlgqSKBQgKOGAVtPY=
github.com/go-chi/jwtauth v4.0.4+incompatible/go.mod h1:Q5EIArY/QnD6BdS+IyDw7B2m6iNbnPxtfd6/BcmtWbs= 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-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.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= 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 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:KKntwkZlM2w/88QiDyAeZ4th8grqtituzMW8qyapYzc=
github.com/jc21/go-sse v0.0.0-20230307071053-2e6b1dbcb7ec/go.mod h1:4v5Xmm0eYuaWqKJ63XUV5YfQPoxtId3DgDytbnWhi+s= 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 h1:7wxq2DIgtO36KLrFz1RldysO0WVvcYsD49G9tyAs01k=
github.com/jc21/jsref v0.0.0-20210608024405-a97debfc4760/go.mod h1:yIq2t51OJgVsdRlPY68NAnyVdBH0kYXxDTFtUxOap80= github.com/jc21/jsref v0.0.0-20210608024405-a97debfc4760/go.mod h1:yIq2t51OJgVsdRlPY68NAnyVdBH0kYXxDTFtUxOap80=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 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/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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 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-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 h1:DDg5u5lk2v8O8qxs8ecQkMUBj3tLW6wkSLzxxOyi1Ig=
github.com/lestrrat-go/structinfo v0.0.0-20210312050401-7f8bd69d6acb/go.mod h1:i+E8Uf04vf2QjOWyJdGY75vmG+4rxiZW2kIj1lTB5mo= 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.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 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-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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13/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.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.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 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 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/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/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 h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 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-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 h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 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 h1:Il6IvLdAapsMhvuOahHWiBnl1G++Q0/L5UIkI5mARSk=
github.com/rotisserie/eris v0.5.4/go.mod h1:Z/kgYTJiJtocxCbFfvRmO+QejApzG6zpyky9G1A4g9s= 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 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.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.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.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 h1:4XIvQTXznxmWMnjouj0ST5lFo/WAYf5Exgl3x82crEk=
github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI= github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI=
github.com/wacul/ptr v1.0.0/go.mod h1:BD0gjsZrCwtoR+yWDB9v2hQ8STlq9tT84qKfa+3txOc= 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= github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJW5OokjraLLRURHiw0OzKHD/RNdspp4w=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-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-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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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-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.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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= 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 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 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 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/sqlite v1.22.1 h1:P2+Dhp5FR1RlVRkQ3dDfCiv3Ok8XPxqpe70IjYVA9oE=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.22.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
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=

View File

@ -35,7 +35,7 @@ func GetAccessLists() func(http.ResponseWriter, *http.Request) {
func GetAccessList() func(http.ResponseWriter, *http.Request) { func GetAccessList() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var accessListID int var accessListID uint
if accessListID, err = getURLParamInt(r, "accessListID"); err != nil { if accessListID, err = getURLParamInt(r, "accessListID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -81,7 +81,7 @@ func CreateAccessList() func(http.ResponseWriter, *http.Request) {
func UpdateAccessList() func(http.ResponseWriter, *http.Request) { func UpdateAccessList() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var accessListID int var accessListID uint
if accessListID, err = getURLParamInt(r, "accessListID"); err != nil { if accessListID, err = getURLParamInt(r, "accessListID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -113,7 +113,7 @@ func UpdateAccessList() func(http.ResponseWriter, *http.Request) {
func DeleteAccessList() func(http.ResponseWriter, *http.Request) { func DeleteAccessList() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var accessListID int var accessListID uint
if accessListID, err = getURLParamInt(r, "accessListID"); err != nil { if accessListID, err = getURLParamInt(r, "accessListID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return

View File

@ -14,9 +14,9 @@ import (
) )
type setAuthModel struct { type setAuthModel struct {
Type string `json:"type" db:"type"` Type string
Secret string `json:"secret,omitempty" db:"secret"` Secret string
CurrentSecret string `json:"current_secret,omitempty"` CurrentSecret string
} }
// SetAuth sets a auth method. This can be used for "me" and `2` for example // SetAuth sets a auth method. This can be used for "me" and `2` for example

View File

@ -38,7 +38,7 @@ func GetCertificateAuthorities() func(http.ResponseWriter, *http.Request) {
func GetCertificateAuthority() func(http.ResponseWriter, *http.Request) { func GetCertificateAuthority() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var caID int var caID uint
if caID, err = getURLParamInt(r, "caID"); err != nil { if caID, err = getURLParamInt(r, "caID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -92,7 +92,7 @@ func CreateCertificateAuthority() func(http.ResponseWriter, *http.Request) {
func UpdateCertificateAuthority() func(http.ResponseWriter, *http.Request) { func UpdateCertificateAuthority() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var caID int var caID uint
if caID, err = getURLParamInt(r, "caID"); err != nil { if caID, err = getURLParamInt(r, "caID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -132,7 +132,7 @@ func UpdateCertificateAuthority() func(http.ResponseWriter, *http.Request) {
func DeleteCertificateAuthority() func(http.ResponseWriter, *http.Request) { func DeleteCertificateAuthority() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var caID int var caID uint
if caID, err = getURLParamInt(r, "caID"); err != nil { if caID, err = getURLParamInt(r, "caID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return

View File

@ -55,7 +55,7 @@ func CreateCertificate() func(http.ResponseWriter, *http.Request) {
var item certificate.Model var item certificate.Model
if fillObjectFromBody(w, r, "", &item) { if fillObjectFromBody(w, r, "", &item) {
// Get userID from token // Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(int) userID, _ := r.Context().Value(c.UserIDCtxKey).(uint)
item.UserID = userID item.UserID = userID
if err := item.Save(); err != nil { 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. // have a certificate id in the url. it will write errors to the output.
func getCertificateFromRequest(w http.ResponseWriter, r *http.Request) *certificate.Model { func getCertificateFromRequest(w http.ResponseWriter, r *http.Request) *certificate.Model {
var err error var err error
var certificateID int var certificateID uint
if certificateID, err = getURLParamInt(r, "certificateID"); err != nil { if certificateID, err = getURLParamInt(r, "certificateID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return nil return nil

View File

@ -38,7 +38,7 @@ func GetDNSProviders() func(http.ResponseWriter, *http.Request) {
func GetDNSProvider() func(http.ResponseWriter, *http.Request) { func GetDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var providerID int var providerID uint
if providerID, err = getURLParamInt(r, "providerID"); err != nil { if providerID, err = getURLParamInt(r, "providerID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -87,7 +87,7 @@ func CreateDNSProvider() func(http.ResponseWriter, *http.Request) {
func UpdateDNSProvider() func(http.ResponseWriter, *http.Request) { func UpdateDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var providerID int var providerID uint
if providerID, err = getURLParamInt(r, "providerID"); err != nil { if providerID, err = getURLParamInt(r, "providerID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -122,7 +122,7 @@ func UpdateDNSProvider() func(http.ResponseWriter, *http.Request) {
func DeleteDNSProvider() func(http.ResponseWriter, *http.Request) { func DeleteDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var providerID int var providerID uint
if providerID, err = getURLParamInt(r, "providerID"); err != nil { if providerID, err = getURLParamInt(r, "providerID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return

View File

@ -4,7 +4,6 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time"
"npm/internal/api/context" "npm/internal/api/context"
"npm/internal/model" "npm/internal/model"
@ -19,11 +18,6 @@ func getPageInfoFromRequest(r *http.Request) (model.PageInfo, error) {
var pageInfo model.PageInfo var pageInfo model.PageInfo
var err error var err error
pageInfo.FromDate, pageInfo.ToDate, err = getDateRanges(r)
if err != nil {
return pageInfo, err
}
pageInfo.Offset, pageInfo.Limit, err = getPagination(r) pageInfo.Offset, pageInfo.Limit, err = getPagination(r)
if err != nil { if err != nil {
return pageInfo, err return pageInfo, err
@ -34,32 +28,6 @@ func getPageInfoFromRequest(r *http.Request) (model.PageInfo, error) {
return pageInfo, nil 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 { func getSortParameter(r *http.Request) []model.Sort {
var sortFields []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 required := true
defaultValue := 0
paramStr := chi.URLParam(r, varName) paramStr := chi.URLParam(r, varName)
var err error
var paramInt int
if paramStr == "" && required { if paramStr == "" && required {
return 0, eris.Errorf("%v was not supplied in the request", varName) 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 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 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) { func getURLParamString(r *http.Request, varName string) (string, error) {

View File

@ -40,7 +40,7 @@ func GetHosts() func(http.ResponseWriter, *http.Request) {
func GetHost() func(http.ResponseWriter, *http.Request) { func GetHost() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var hostID int var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil { if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -74,7 +74,7 @@ func CreateHost() func(http.ResponseWriter, *http.Request) {
} }
// Get userID from token // Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(int) userID, _ := r.Context().Value(c.UserIDCtxKey).(uint)
newHost.UserID = userID newHost.UserID = userID
if err = validator.ValidateHost(newHost); err != nil { 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) { func UpdateHost() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var hostID int var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil { if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -148,7 +148,7 @@ func UpdateHost() func(http.ResponseWriter, *http.Request) {
func DeleteHost() func(http.ResponseWriter, *http.Request) { func DeleteHost() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var hostID int var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil { if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -173,7 +173,7 @@ func DeleteHost() func(http.ResponseWriter, *http.Request) {
func GetHostNginxConfig(format string) func(http.ResponseWriter, *http.Request) { func GetHostNginxConfig(format string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var hostID int var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil { if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return

View File

@ -36,7 +36,7 @@ func GetNginxTemplates() func(http.ResponseWriter, *http.Request) {
func GetNginxTemplate() func(http.ResponseWriter, *http.Request) { func GetNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var templateID int var templateID uint
if templateID, err = getURLParamInt(r, "templateID"); err != nil { if templateID, err = getURLParamInt(r, "templateID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -85,7 +85,7 @@ func CreateNginxTemplate() func(http.ResponseWriter, *http.Request) {
func UpdateNginxTemplate() func(http.ResponseWriter, *http.Request) { func UpdateNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var templateID int var templateID uint
if templateID, err = getURLParamInt(r, "templateID"); err != nil { if templateID, err = getURLParamInt(r, "templateID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -122,7 +122,7 @@ func UpdateNginxTemplate() func(http.ResponseWriter, *http.Request) {
func DeleteNginxTemplate() func(http.ResponseWriter, *http.Request) { func DeleteNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var templateID int var templateID uint
if templateID, err = getURLParamInt(r, "templateID"); err != nil { if templateID, err = getURLParamInt(r, "templateID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return

View File

@ -36,7 +36,7 @@ func GetStreams() func(http.ResponseWriter, *http.Request) {
func GetStream() func(http.ResponseWriter, *http.Request) { func GetStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var hostID int var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil { if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -85,7 +85,7 @@ func CreateStream() func(http.ResponseWriter, *http.Request) {
func UpdateStream() func(http.ResponseWriter, *http.Request) { func UpdateStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var hostID int var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil { if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -120,7 +120,7 @@ func UpdateStream() func(http.ResponseWriter, *http.Request) {
func DeleteStream() func(http.ResponseWriter, *http.Request) { func DeleteStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var hostID int var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil { if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return

View File

@ -93,7 +93,7 @@ func RefreshToken() func(http.ResponseWriter, *http.Request) {
// Route: POST /tokens/sse // Route: POST /tokens/sse
func NewSSEToken() func(http.ResponseWriter, *http.Request) { func NewSSEToken() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *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 // Find user
userObj, userErr := user.GetByID(userID) userObj, userErr := user.GetByID(userID)

View File

@ -41,7 +41,7 @@ func GetUpstreams() func(http.ResponseWriter, *http.Request) {
func GetUpstream() func(http.ResponseWriter, *http.Request) { func GetUpstream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var upstreamID int var upstreamID uint
if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil { if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -75,7 +75,7 @@ func CreateUpstream() func(http.ResponseWriter, *http.Request) {
} }
// Get userID from token // Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(int) userID, _ := r.Context().Value(c.UserIDCtxKey).(uint)
newUpstream.UserID = userID newUpstream.UserID = userID
if err = validator.ValidateUpstream(newUpstream); err != nil { 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) { func UpdateUpstream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var upstreamID int var upstreamID uint
if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil { if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -141,7 +141,7 @@ func UpdateUpstream() func(http.ResponseWriter, *http.Request) {
func DeleteUpstream() func(http.ResponseWriter, *http.Request) { func DeleteUpstream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var upstreamID int var upstreamID uint
if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil { if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
@ -172,7 +172,7 @@ func DeleteUpstream() func(http.ResponseWriter, *http.Request) {
func GetUpstreamNginxConfig(format string) func(http.ResponseWriter, *http.Request) { func GetUpstreamNginxConfig(format string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var err error var err error
var upstreamID int var upstreamID uint
if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil { if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return

View File

@ -121,14 +121,14 @@ func UpdateUser() func(http.ResponseWriter, *http.Request) {
// Route: DELETE /users/{userID} // Route: DELETE /users/{userID}
func DeleteUser() func(http.ResponseWriter, *http.Request) { func DeleteUser() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var userID int var userID uint
var err error var err error
if userID, err = getURLParamInt(r, "userID"); err != nil { if userID, err = getURLParamInt(r, "userID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return return
} }
myUserID, _ := r.Context().Value(c.UserIDCtxKey).(int) myUserID, _ := r.Context().Value(c.UserIDCtxKey).(uint)
if myUserID == userID { if myUserID == userID {
h.ResultErrorJSON(w, r, http.StatusBadRequest, "You cannot delete yourself!", nil) h.ResultErrorJSON(w, r, http.StatusBadRequest, "You cannot delete yourself!", nil)
return 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") 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 self := false
if userIDstr == "me" { if userIDstr == "me" {
// Get user id from Token // Get user id from Token

View File

@ -48,7 +48,7 @@ func Enforce(permission string) func(http.Handler) http.Handler {
return return
} }
userID := int(claims["uid"].(float64)) userID := uint(claims["uid"].(float64))
_, enabled := user.IsEnabled(userID) _, enabled := user.IsEnabled(userID)
if token == nil || !token.Valid || !enabled { if token == nil || !token.Valid || !enabled {
h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil) h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil)

View File

@ -21,7 +21,7 @@ func SSEAuth(next http.Handler) http.Handler {
return return
} }
userID := int(claims["uid"].(float64)) userID := uint(claims["uid"].(float64))
_, enabled := user.IsEnabled(userID) _, enabled := user.IsEnabled(userID)
if token == nil || !token.Valid || !enabled || !claims.VerifyIssuer("sse", true) { if token == nil || !token.Valid || !enabled || !claims.VerifyIssuer("sse", true) {
h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil) h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil)

View File

@ -8,15 +8,6 @@ import (
"npm/internal/api/middleware" "npm/internal/api/middleware"
"npm/internal/api/schema" "npm/internal/api/schema"
"npm/internal/config" "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/entity/user"
"npm/internal/logger" "npm/internal/logger"
"npm/internal/serverevents" "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) { r.With(middleware.Enforce(user.CapabilityUsersManage)).Route("/", func(r chi.Router) {
// List // 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()) Get("/", handler.GetUsers())
// Specific Item // Specific Item
@ -132,8 +124,8 @@ func applyRoutes(r chi.Router) chi.Router {
// Settings // Settings
r.With(middleware.EnforceSetup(true), middleware.Enforce(user.CapabilitySettingsManage)).Route("/settings", func(r chi.Router) { r.With(middleware.EnforceSetup(true), middleware.Enforce(user.CapabilitySettingsManage)).Route("/settings", func(r chi.Router) {
r.With(middleware.Filters(setting.GetFilterSchema())). // r.With(middleware.Filters(setting.GetFilterSchema())).
Get("/", handler.GetSettings()) r.Get("/", handler.GetSettings())
r.Get("/{name}", handler.GetSetting()) r.Get("/{name}", handler.GetSetting())
r.With(middleware.EnforceRequestSchema(schema.CreateSetting())). r.With(middleware.EnforceRequestSchema(schema.CreateSetting())).
Post("/", handler.CreateSetting()) Post("/", handler.CreateSetting())
@ -144,7 +136,8 @@ func applyRoutes(r chi.Router) chi.Router {
// Access Lists // Access Lists
r.With(middleware.EnforceSetup(true)).Route("/access-lists", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/access-lists", func(r chi.Router) {
// List // 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()) Get("/", handler.GetAccessLists())
// Create // Create
@ -166,7 +159,8 @@ func applyRoutes(r chi.Router) chi.Router {
// DNS Providers // DNS Providers
r.With(middleware.EnforceSetup(true)).Route("/dns-providers", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/dns-providers", func(r chi.Router) {
// List // 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()) Get("/", handler.GetDNSProviders())
// Create // Create
@ -194,7 +188,8 @@ func applyRoutes(r chi.Router) chi.Router {
// Certificate Authorities // Certificate Authorities
r.With(middleware.EnforceSetup(true)).Route("/certificate-authorities", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/certificate-authorities", func(r chi.Router) {
// List // 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()) Get("/", handler.GetCertificateAuthorities())
// Create // Create
@ -216,7 +211,8 @@ func applyRoutes(r chi.Router) chi.Router {
// Certificates // Certificates
r.With(middleware.EnforceSetup(true)).Route("/certificates", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/certificates", func(r chi.Router) {
// List // 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()) Get("/", handler.GetCertificates())
// Create // Create
@ -241,7 +237,8 @@ func applyRoutes(r chi.Router) chi.Router {
// Hosts // Hosts
r.With(middleware.EnforceSetup(true)).Route("/hosts", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/hosts", func(r chi.Router) {
// List // 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()) Get("/", handler.GetHosts())
// Create // Create
@ -265,7 +262,8 @@ func applyRoutes(r chi.Router) chi.Router {
// Nginx Templates // Nginx Templates
r.With(middleware.EnforceSetup(true)).Route("/nginx-templates", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/nginx-templates", func(r chi.Router) {
// List // 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()) Get("/", handler.GetNginxTemplates())
// Create // Create
@ -287,7 +285,8 @@ func applyRoutes(r chi.Router) chi.Router {
// Streams // Streams
r.With(middleware.EnforceSetup(true)).Route("/streams", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/streams", func(r chi.Router) {
// List // 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()) Get("/", handler.GetStreams())
// Create // Create
@ -309,7 +308,8 @@ func applyRoutes(r chi.Router) chi.Router {
// Upstreams // Upstreams
r.With(middleware.EnforceSetup(true)).Route("/upstreams", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/upstreams", func(r chi.Router) {
// List // 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()) Get("/", handler.GetUpstreams())
// Create // Create

View File

@ -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)
}

View File

@ -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 ""
}

View File

@ -1,7 +1,6 @@
package config package config
import ( import (
"fmt"
"npm/internal/logger" "npm/internal/logger"
) )
@ -30,22 +29,12 @@ type log struct {
Format string `json:"format" envconfig:"optional,default=nice"` 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 // Configuration is the main configuration object
var Configuration struct { var Configuration struct {
DataFolder string `json:"data_folder" envconfig:"optional,default=/data"` DataFolder string `json:"data_folder" envconfig:"optional,default=/data"`
DisableIPV4 bool `json:"disable_ipv4" envconfig:"optional"` DisableIPV4 bool `json:"disable_ipv4" envconfig:"optional"`
DisableIPV6 bool `json:"disable_ipv6" envconfig:"optional"` DisableIPV6 bool `json:"disable_ipv6" envconfig:"optional"`
Acmesh acmesh `json:"acmesh"` Acmesh acmesh `json:"acmesh"`
DB db `json:"db"`
Log log `json:"log"` Log log `json:"log"`
} }
// GetWellknown returns the well known path
func (a *acmesh) GetWellknown() string {
return fmt.Sprintf("%s/.well-known", a.Home)
}

View File

@ -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)
}
}
}
*/

View File

@ -1,46 +1,8 @@
package database package database
import (
"fmt"
"strings"
"npm/internal/errors"
"npm/internal/model"
"npm/internal/util"
)
const ( const (
// DateFormat for DateFormat // DateFormat for DateFormat
DateFormat = "2006-01-02" DateFormat = "2006-01-02"
// DateTimeFormat for DateTimeFormat // DateTimeFormat for DateTimeFormat
DateTimeFormat = "2006-01-02T15:04:05" 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
}

View File

@ -1,203 +1,44 @@
package database package database
import ( import (
"database/sql"
"fmt" "fmt"
"io/fs" "net/url"
"path"
"path/filepath"
"strings"
"sync"
"time"
"npm/embed" "npm/embed"
"npm/internal/config"
"npm/internal/logger" "npm/internal/logger"
"npm/internal/util"
"github.com/jmoiron/sqlx" "github.com/amacneil/dbmate/v2/pkg/dbmate"
"github.com/rotisserie/eris" _ "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() 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 { 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 migrations, err := db.FindMigrations()
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 { if err != nil {
logger.Error("MigratorError", err) logger.Error("MigrationError", err)
return false return false
} }
logger.Info("Migration: Migration Table created")
for _, m := range migrations {
logger.Debug("%s: %s", m.Version, m.FilePath)
} }
// DO MIGRATION err = db.CreateAndMigrate()
migrationCount, migrateErr := performFileMigrations(db) if err != nil {
if migrateErr != nil { logger.Error("MigrationError", err)
logger.Error("MigratorError", migrateErr) return false
} }
if migrateErr == nil {
logger.Info("Migration: Completed %v migration files", migrationCount)
followup() followup()
return true 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)
if err != nil {
if err == sql.ErrNoRows {
return existingMigrations, nil
}
return existingMigrations, err
}
for rows.Next() {
var filename *string
err := rows.Scan(&filename)
if err != nil {
return existingMigrations, err
}
existingMigrations = append(existingMigrations, *filename)
}
return existingMigrations, nil
}
// 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
} }

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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)
}

View File

@ -1,122 +1,41 @@
package accesslist package accesslist
import ( import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a row by ID // GetByID finds a row by ID
func GetByID(id int) (Model, error) { func GetByID(id uint) (Model, error) {
var m Model var m Model
err := m.LoadByID(id) err := m.LoadByID(id)
return m, err 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 // List will return a list of access lists
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "name", Field: "name",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Error("ListAccessListsError", queryErr)
logger.Debug("%s -- %+v", query, params)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Error("ListAccessListsError", err)
logger.Debug("%s -- %+v", query, params)
return result, err
} }
result = ListResponse{ result = entity.ListResponse{
Items: items, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, Limit: pageInfo.Limit,

View File

@ -1,77 +1,54 @@
package accesslist package accesslist
import ( import (
"fmt"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity"
"npm/internal/entity/user" "npm/internal/entity/user"
"npm/internal/types" "npm/internal/types"
"github.com/rotisserie/eris" "github.com/rotisserie/eris"
) )
const ( // Model is the model
tableName = "access_list"
)
// Model is the access list model
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` UserID int `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"`
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` Meta types.JSONB `json:"meta" gorm:"column:meta"`
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"`
// Expansions // Expansions
User *user.Model `json:"user,omitempty"` User *user.Model `json:"user,omitempty" gorm:"-"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "access_list"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id uint) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id, 0} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
}
// 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
} }
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save() error { func (m *Model) Save() error {
var err error
if m.UserID == 0 { if m.UserID == 0 {
return eris.Errorf("User ID must be specified") return eris.Errorf("User ID must be specified")
} }
if m.ID == 0 { db := database.GetDB()
m.ID, err = Create(m) result := db.Save(m)
} else { return result.Error
err = Update(m)
}
return err
} }
// Delete will mark a access list as deleted // Delete will mark row as deleted
func (m *Model) Delete() bool { func (m *Model) Delete() bool {
m.Touch(false) if m.ID == 0 {
m.IsDeleted = true // Can't delete a new object
if err := m.Save(); err != nil {
return false return false
} }
return true db := database.GetDB()
result := db.Delete(m)
return result.Error == nil
} }

View File

@ -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"`
}

View File

@ -1,11 +1,7 @@
package auth package auth
import ( import (
"fmt"
"npm/internal/database" "npm/internal/database"
"github.com/rotisserie/eris"
) )
// GetByID finds a auth by ID // GetByID finds a auth by ID
@ -16,12 +12,17 @@ func GetByID(id int) (Model, error) {
} }
// GetByUserIDType finds a user by email // GetByUserIDType finds a user by email
func GetByUserIDType(userID int, authType string) (Model, error) { func GetByUserIDType(userID uint, authType string) (Model, error) {
var m Model var auth Model
err := m.LoadByUserIDType(userID, authType) db := database.GetDB()
return m, err result := db.
Where("user_id = ?", userID).
Where("type = ?", authType).
First(&auth)
return auth, result.Error
} }
/*
// Create will create a Auth from this model // Create will create a Auth from this model
func Create(auth *Model) (int, error) { func Create(auth *Model) (int, error) {
if auth.ID != 0 { if auth.ID != 0 {
@ -59,7 +60,9 @@ func Create(auth *Model) (int, error) {
return int(last), nil return int(last), nil
} }
*/
/*
// Update will Update a Auth from this model // Update will Update a Auth from this model
func Update(auth *Model) error { func Update(auth *Model) error {
if auth.ID == 0 { if auth.ID == 0 {
@ -81,3 +84,4 @@ func Update(auth *Model) error {
return err return err
} }
*/

View File

@ -1,52 +1,49 @@
package auth package auth
import ( import (
"fmt"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/types" "npm/internal/entity"
"github.com/rotisserie/eris" "github.com/rotisserie/eris"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
const ( const (
tableName = "auth"
// TypePassword is the Password Type // TypePassword is the Password Type
TypePassword = "password" TypePassword = "password"
) )
// Model is the user model // Model is the model
type Model struct { type Model struct {
ID int `json:"id" db:"id"` entity.ModelBase
UserID int `json:"user_id" db:"user_id"` UserID uint `json:"user_id" gorm:"column:user_id"`
Type string `json:"type" db:"type"` Type string `json:"type" gorm:"column:type;default:password"`
Secret string `json:"secret,omitempty" db:"secret"` Secret string `json:"secret,omitempty" gorm:"column: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"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "auth"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id int) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
} }
// LoadByUserIDType will load from an ID // LoadByUserIDType will load from an ID
func (m *Model) LoadByUserIDType(userID int, authType string) error { func (m *Model) LoadByUserIDType(userID int, authType string) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE user_id = ? AND type = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{userID, authType} result := db.
return m.getByQuery(query, params) Where("user_id = ?", userID).
Where("type = ?", authType).
First(&m)
return result.Error
} }
/*
// Touch will update model's timestamp(s) // Touch will update model's timestamp(s)
func (m *Model) Touch(created bool) { func (m *Model) Touch(created bool) {
var d types.DBDate var d types.DBDate
@ -56,18 +53,14 @@ func (m *Model) Touch(created bool) {
} }
m.ModifiedOn = d m.ModifiedOn = d
} }
*/
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save() error { func (m *Model) Save() error {
var err error db := database.GetDB()
// todo: touch? not sure that save does this or not?
if m.ID == 0 { result := db.Save(m)
m.ID, err = Create(m) return result.Error
} else {
err = Update(m)
}
return err
} }
// SetPassword will generate a hashed password based on given string // SetPassword will generate a hashed password based on given string

View File

@ -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"
}

View File

@ -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)
}

View File

@ -1,142 +1,54 @@
package certificate package certificate
import ( import (
"database/sql"
"fmt"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/jobqueue" "npm/internal/jobqueue"
"npm/internal/logger" "npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a row by ID // GetByID finds a row by ID
func GetByID(id int) (Model, error) { func GetByID(id uint) (Model, error) {
var m Model var m Model
err := m.LoadByID(id) err := m.LoadByID(id)
return m, err return m, err
} }
// Create will create a row from this model // GetByStatus will select rows that are ready for requesting
func Create(certificate *Model) (int, error) { func GetByStatus(status string) ([]Model, error) {
if certificate.ID != 0 { items := make([]Model, 0)
return 0, eris.New("Cannot create certificate when model already has an ID") db := database.GetDB()
} result := db.
Joins("INNER JOIN certificate_authority ON certificate_authority.id = certificate.certificate_authority_id AND certificate_authority.is_deleted = ?", 0).
certificate.Touch(true) Where("type IN ?", []string{"http", "dns"}).
Where("status = ?", status).
db := database.GetInstance() Where("certificate_authority_id > ?", 0).
// nolint: gosec Find(&items)
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` ( return items, result.Error
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
} }
// List will return a list of certificates // List will return a list of certificates
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "name", Field: "name",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
} }
if expand != nil { 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, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, Limit: pageInfo.Limit,
@ -160,33 +72,6 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis
return result, nil 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 // AddPendingJobs is intended to be used at startup to add
// anything pending to the JobQueue just once, based on // anything pending to the JobQueue just once, based on
// the database row status // the database row status

View File

@ -10,6 +10,7 @@ import (
"npm/internal/acme" "npm/internal/acme"
"npm/internal/config" "npm/internal/config"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity"
"npm/internal/entity/certificateauthority" "npm/internal/entity/certificateauthority"
"npm/internal/entity/dnsprovider" "npm/internal/entity/dnsprovider"
"npm/internal/entity/user" "npm/internal/entity/user"
@ -22,8 +23,6 @@ import (
) )
const ( const (
tableName = "certificate"
// TypeCustom custom cert type // TypeCustom custom cert type
TypeCustom = "custom" TypeCustom = "custom"
// TypeHTTP http cert type // TypeHTTP http cert type
@ -43,54 +42,40 @@ const (
StatusProvided = "provided" StatusProvided = "provided"
) )
// Model is the user model // Model is the model
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` ExpiresOn types.NullableDBDate `json:"expires_on" gorm:"column:expires_on" filter:"expires_on,integer"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` Type string `json:"type" gorm:"column:type" filter:"type,string"`
ExpiresOn types.NullableDBDate `json:"expires_on" db:"expires_on" filter:"expires_on,integer"` UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
Type string `json:"type" db:"type" filter:"type,string"` CertificateAuthorityID uint `json:"certificate_authority_id" gorm:"column:certificate_authority_id" filter:"certificate_authority_id,integer"`
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` DNSProviderID uint `json:"dns_provider_id" gorm:"column:dns_provider_id" filter:"dns_provider_id,integer"`
CertificateAuthorityID int `json:"certificate_authority_id" db:"certificate_authority_id" filter:"certificate_authority_id,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"`
DNSProviderID int `json:"dns_provider_id" db:"dns_provider_id" filter:"dns_provider_id,integer"` DomainNames types.JSONB `json:"domain_names" gorm:"column:domain_names" filter:"domain_names,string"`
Name string `json:"name" db:"name" filter:"name,string"` Status string `json:"status" gorm:"column:status" filter:"status,string"`
DomainNames types.JSONB `json:"domain_names" db:"domain_names" filter:"domain_names,string"` ErrorMessage string `json:"error_message" gorm:"column:error_message" filter:"error_message,string"`
Status string `json:"status" db:"status" filter:"status,string"` Meta types.JSONB `json:"-" gorm:"column:meta"`
ErrorMessage string `json:"error_message" db:"error_message" filter:"error_message,string"` IsECC bool `json:"is_ecc" gorm:"column:is_ecc" filter:"is_ecc,bool"`
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"`
// Expansions: // Expansions:
CertificateAuthority *certificateauthority.Model `json:"certificate_authority,omitempty"` CertificateAuthority *certificateauthority.Model `json:"certificate_authority,omitempty" gorm:"-"`
DNSProvider *dnsprovider.Model `json:"dns_provider,omitempty"` DNSProvider *dnsprovider.Model `json:"dns_provider,omitempty" gorm:"-"`
User *user.Model `json:"user,omitempty"` User *user.Model `json:"user,omitempty" gorm:"-"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "certificate"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id uint) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id, 0} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
}
// 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
} }
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save() error { func (m *Model) Save() error {
var err error
if m.UserID == 0 { if m.UserID == 0 {
return eris.Errorf("User ID must be specified") return eris.Errorf("User ID must be specified")
} }
@ -108,26 +93,22 @@ func (m *Model) Save() error {
// ensure name is trimmed of whitespace // ensure name is trimmed of whitespace
m.Name = strings.TrimSpace(m.Name) m.Name = strings.TrimSpace(m.Name)
if m.ID == 0 { db := database.GetDB()
m.ID, err = Create(m) result := db.Save(m)
} else { return result.Error
err = Update(m)
}
return err
} }
// Delete will mark a certificate as deleted // Delete will mark row as deleted
func (m *Model) Delete() bool { func (m *Model) Delete() bool {
m.Touch(false) if m.ID == 0 {
m.IsDeleted = true // Can't delete a new object
if err := m.Save(); err != nil {
return false return false
} }
db := database.GetDB()
result := db.Delete(m)
return result.Error == nil
// todo: delete from acme.sh as well // todo: delete from acme.sh as well
return true
} }
// Validate will make sure the data given is expected. This object is a bit complicated, // 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{ return Template{
ID: m.ID, ID: m.ID,
CreatedOn: m.CreatedOn.Time.String(), CreatedAt: fmt.Sprintf("%d", m.CreatedAt), // todo: nice date string
ModifiedOn: m.ModifiedOn.Time.String(), UpdatedAt: fmt.Sprintf("%d", m.UpdatedAt), // todo: nice date string
ExpiresOn: m.ExpiresOn.AsString(), ExpiresOn: m.ExpiresOn.AsString(),
Type: m.Type, Type: m.Type,
UserID: m.UserID, UserID: m.UserID,

View File

@ -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"`
}

View File

@ -2,14 +2,14 @@ package certificate
// Template is the model given to the template parser, converted from the Model // Template is the model given to the template parser, converted from the Model
type Template struct { type Template struct {
ID int ID uint
CreatedOn string CreatedAt string
ModifiedOn string UpdatedAt string
ExpiresOn string ExpiresOn string
Type string Type string
UserID int UserID uint
CertificateAuthorityID int CertificateAuthorityID uint
DNSProviderID int DNSProviderID uint
Name string Name string
DomainNames []string DomainNames []string
Status string Status string

View File

@ -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)
}

View File

@ -1,128 +1,41 @@
package certificateauthority package certificateauthority
import ( import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a row by ID // GetByID finds a row by ID
func GetByID(id int) (Model, error) { func GetByID(id uint) (Model, error) {
var m Model var m Model
err := m.LoadByID(id) err := m.LoadByID(id)
return m, err 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 // List will return a list of certificates
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "name", Field: "name",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Error("ListCertificateAuthoritiesError", queryErr)
logger.Debug("%s -- %+v", query, params)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Error("ListCertificateAuthoritiesError", err)
logger.Debug("%s -- %+v", query, params)
return result, err
} }
result = ListResponse{ result = entity.ListResponse{
Items: items, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, Limit: pageInfo.Limit,

View File

@ -1,78 +1,55 @@
package certificateauthority package certificateauthority
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity"
"npm/internal/errors" "npm/internal/errors"
"npm/internal/types"
"github.com/rotisserie/eris" "github.com/rotisserie/eris"
) )
const ( // Model is the model
tableName = "certificate_authority"
)
// Model is the user model
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` AcmeshServer string `json:"acmesh_server" gorm:"column:acmesh_server" filter:"acmesh_server,string"`
Name string `json:"name" db:"name" filter:"name,string"` CABundle string `json:"ca_bundle" gorm:"column:ca_bundle" filter:"ca_bundle,string"`
AcmeshServer string `json:"acmesh_server" db:"acmesh_server" filter:"acmesh_server,string"` MaxDomains int `json:"max_domains" gorm:"column:max_domains" filter:"max_domains,integer"`
CABundle string `json:"ca_bundle" db:"ca_bundle" filter:"ca_bundle,string"` IsWildcardSupported bool `json:"is_wildcard_supported" gorm:"column:is_wildcard_supported" filter:"is_wildcard_supported,boolean"`
MaxDomains int `json:"max_domains" db:"max_domains" filter:"max_domains,integer"` IsReadonly bool `json:"is_readonly" gorm:"column:is_readonly" filter:"is_readonly,boolean"`
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"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "certificate_authority"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id uint) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id, 0} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
}
// 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
} }
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save() error { func (m *Model) Save() error {
var err error db := database.GetDB()
result := db.Save(m)
if m.ID == 0 { return result.Error
m.ID, err = Create(m)
} else {
err = Update(m)
}
return err
} }
// Delete will mark a certificate as deleted // Delete will mark row as deleted
func (m *Model) Delete() bool { func (m *Model) Delete() bool {
m.Touch(false) if m.ID == 0 {
m.IsDeleted = true // Can't delete a new object
if err := m.Save(); err != nil {
return false 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 // Check will ensure the ca bundle path exists if it's set

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -1,128 +1,41 @@
package dnsprovider package dnsprovider
import ( import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a row by ID // GetByID finds a row by ID
func GetByID(id int) (Model, error) { func GetByID(id uint) (Model, error) {
var m Model var m Model
err := m.LoadByID(id) err := m.LoadByID(id)
return m, err 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 // List will return a list of certificates
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "name", Field: "name",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Error("ListDnsProvidersError", queryErr)
logger.Debug("%s -- %+v", query, params)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Error("ListDnsProvidersError", err)
logger.Debug("%s -- %+v", query, params)
return result, err
} }
result = ListResponse{ result = entity.ListResponse{
Items: items, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, Limit: pageInfo.Limit,

View File

@ -2,80 +2,58 @@ package dnsprovider
import ( import (
"fmt" "fmt"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/dnsproviders" "npm/internal/dnsproviders"
"npm/internal/entity"
"npm/internal/logger" "npm/internal/logger"
"npm/internal/types" "npm/internal/types"
"github.com/rotisserie/eris" "github.com/rotisserie/eris"
) )
const ( // Model is the model
tableName = "dns_provider"
)
// Model is the user model
// Also see: https://github.com/acmesh-official/acme.sh/wiki/dnscheck
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` UserID int `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"`
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` AcmeshName string `json:"acmesh_name" gorm:"column:acmesh_name" filter:"acmesh_name,string"`
Name string `json:"name" db:"name" filter:"name,string"` DNSSleep int `json:"dns_sleep" gorm:"column:dns_sleep" filter:"dns_sleep,integer"`
AcmeshName string `json:"acmesh_name" db:"acmesh_name" filter:"acmesh_name,string"` Meta types.JSONB `json:"meta" gorm:"column:meta"`
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"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "dns_provider"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id uint) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id, 0} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
}
// 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
} }
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save() error { func (m *Model) Save() error {
var err error
if m.UserID == 0 { if m.UserID == 0 {
return eris.Errorf("User ID must be specified") return eris.Errorf("User ID must be specified")
} }
if m.ID == 0 { db := database.GetDB()
m.ID, err = Create(m) result := db.Save(m)
} else { return result.Error
err = Update(m)
}
return err
} }
// Delete will mark a certificate as deleted // Delete will mark row as deleted
func (m *Model) Delete() bool { func (m *Model) Delete() bool {
m.Touch(false) if m.ID == 0 {
m.IsDeleted = true // Can't delete a new object
if err := m.Save(); err != nil {
return false 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 // GetAcmeShEnvVars returns the env vars required for acme.sh dns cert requests

View File

@ -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"`
}

View File

@ -11,12 +11,6 @@ import (
// FilterMapFunction is a filter map function // FilterMapFunction is a filter map function
type FilterMapFunction func(value []string) []string 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 // GenerateSQLFromFilters will return a Query and params for use as WHERE clause in SQL queries
// This will use a AND where clause approach. // This will use a AND where clause approach.
func GenerateSQLFromFilters(filters []model.Filter, fieldMap map[string]string, fieldMapFunctions map[string]FilterMapFunction) (string, []interface{}) { 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 return clause
} }
/*
// GetFilterMap returns the filter map // GetFilterMap returns the filter map
func GetFilterMap(m interface{}) map[string]string { func GetFilterMap(m interface{}) map[string]string {
var filterMap = make(map[string]string) var filterMap = make(map[string]string)
@ -110,8 +105,8 @@ func GetFilterMap(m interface{}) map[string]string {
field := t.Field(i) field := t.Field(i)
// Get the field tag value // Get the field tag value
filterTag := field.Tag.Get(FilterTagName) filterTag := field.Tag.Get("filter")
dbTag := field.Tag.Get(DBTagName) dbTag := field.Tag.Get("db")
if filterTag != "" && dbTag != "" && dbTag != "-" && filterTag != "-" { if filterTag != "" && dbTag != "" && dbTag != "-" && filterTag != "-" {
// Filter tag can be a 2 part thing: name,type // Filter tag can be a 2 part thing: name,type
// ie: account_id,integer // ie: account_id,integer
@ -124,6 +119,7 @@ func GetFilterMap(m interface{}) map[string]string {
return filterMap return filterMap
} }
*/
// GetDBColumns returns the db columns // GetDBColumns returns the db columns
func GetDBColumns(m interface{}) []string { func GetDBColumns(m interface{}) []string {
@ -132,7 +128,7 @@ func GetDBColumns(m interface{}) []string {
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
field := t.Field(i) field := t.Field(i)
dbTag := field.Tag.Get(DBTagName) dbTag := field.Tag.Get("db")
if dbTag != "" && dbTag != "-" { if dbTag != "" && dbTag != "-" {
columns = append(columns, dbTag) columns = append(columns, dbTag)
} }

View File

@ -14,7 +14,7 @@ func GetFilterSchema(m interface{}) string {
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
field := t.Field(i) field := t.Field(i)
filterTag := field.Tag.Get(FilterTagName) filterTag := field.Tag.Get("filter")
if filterTag != "" && filterTag != "-" { if filterTag != "" && filterTag != "-" {
// split out tag value "field,filtreType" // split out tag value "field,filtreType"

View File

@ -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)
}

View File

@ -1,181 +1,40 @@
package host package host
import ( import (
"database/sql"
"fmt"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger" "npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a Host by ID // GetByID finds a Host by ID
func GetByID(id int) (Model, error) { func GetByID(id uint) (Model, error) {
var m Model var m Model
err := m.LoadByID(id) err := m.LoadByID(id)
return m, err 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 // List will return a list of hosts
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "domain_names", Field: "domain_names",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
} }
if expand != nil { 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, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, 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 // GetUpstreamUseCount returns the number of hosts that are using
// an upstream, and have not been deleted. // an upstream, and have not been deleted.
func GetUpstreamUseCount(upstreamID int) int { func GetUpstreamUseCount(upstreamID uint) int64 {
db := database.GetInstance() db := database.GetDB()
query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE upstream_id = ? AND is_deleted = ?", tableName)
countRow := db.QueryRowx(query, upstreamID, 0) var count int64
var totalRows int if result := db.Model(&Model{}).Where("upstream_id = ?", upstreamID).Count(&count); result.Error != nil {
queryErr := countRow.Scan(&totalRows) logger.Debug("GetUpstreamUseCount Error: %v", result.Error)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s", query)
return 0 return 0
} }
return totalRows return count
} }
// GetCertificateUseCount returns the number of hosts that are using // GetCertificateUseCount returns the number of hosts that are using
// a certificate, and have not been deleted. // a certificate, and have not been deleted.
func GetCertificateUseCount(certificateID int) int { func GetCertificateUseCount(certificateID uint) int64 {
db := database.GetInstance() db := database.GetDB()
query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE certificate_id = ? AND is_deleted = ?", tableName)
countRow := db.QueryRowx(query, certificateID, 0) var count int64
var totalRows int if result := db.Model(&Model{}).Where("certificate_id = ?", certificateID).Count(&count); result.Error != nil {
queryErr := countRow.Scan(&totalRows) logger.Debug("GetUpstreamUseCount Error: %v", result.Error)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s", query)
return 0 return 0
} }
return totalRows return count
} }
// AddPendingJobs is intended to be used at startup to add // AddPendingJobs is intended to be used at startup to add

View File

@ -2,9 +2,8 @@ package host
import ( import (
"fmt" "fmt"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity"
"npm/internal/entity/certificate" "npm/internal/entity/certificate"
"npm/internal/entity/nginxtemplate" "npm/internal/entity/nginxtemplate"
"npm/internal/entity/upstream" "npm/internal/entity/upstream"
@ -17,8 +16,6 @@ import (
) )
const ( const (
tableName = "host"
// ProxyHostType is self explanatory // ProxyHostType is self explanatory
ProxyHostType = "proxy" ProxyHostType = "proxy"
// RedirectionHostType is self explanatory // RedirectionHostType is self explanatory
@ -27,67 +24,53 @@ const (
DeadHostType = "dead" DeadHostType = "dead"
) )
// Model is the user model // Model is the model
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` Type string `json:"type" gorm:"column:type" filter:"type,string"`
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` NginxTemplateID uint `json:"nginx_template_id" gorm:"column:nginx_template_id" filter:"nginx_template_id,integer"`
Type string `json:"type" db:"type" filter:"type,string"` ListenInterface string `json:"listen_interface" gorm:"column:listen_interface" filter:"listen_interface,string"`
NginxTemplateID int `json:"nginx_template_id" db:"nginx_template_id" filter:"nginx_template_id,integer"` DomainNames types.JSONB `json:"domain_names" gorm:"column:domain_names" filter:"domain_names,string"`
ListenInterface string `json:"listen_interface" db:"listen_interface" filter:"listen_interface,string"` UpstreamID uint `json:"upstream_id" gorm:"column:upstream_id" filter:"upstream_id,integer"`
DomainNames types.JSONB `json:"domain_names" db:"domain_names" filter:"domain_names,string"` ProxyScheme string `json:"proxy_scheme" gorm:"column:proxy_scheme" filter:"proxy_scheme,string"`
UpstreamID int `json:"upstream_id" db:"upstream_id" filter:"upstream_id,integer"` ProxyHost string `json:"proxy_host" gorm:"column:proxy_host" filter:"proxy_host,string"`
ProxyScheme string `json:"proxy_scheme" db:"proxy_scheme" filter:"proxy_scheme,string"` ProxyPort int `json:"proxy_port" gorm:"column:proxy_port" filter:"proxy_port,integer"`
ProxyHost string `json:"proxy_host" db:"proxy_host" filter:"proxy_host,string"` CertificateID uint `json:"certificate_id" gorm:"column:certificate_id" filter:"certificate_id,integer"`
ProxyPort int `json:"proxy_port" db:"proxy_port" filter:"proxy_port,integer"` AccessListID uint `json:"access_list_id" gorm:"column:access_list_id" filter:"access_list_id,integer"`
CertificateID int `json:"certificate_id" db:"certificate_id" filter:"certificate_id,integer"` SSLForced bool `json:"ssl_forced" gorm:"column:ssl_forced" filter:"ssl_forced,boolean"`
AccessListID int `json:"access_list_id" db:"access_list_id" filter:"access_list_id,integer"` CachingEnabled bool `json:"caching_enabled" gorm:"column:caching_enabled" filter:"caching_enabled,boolean"`
SSLForced bool `json:"ssl_forced" db:"ssl_forced" filter:"ssl_forced,boolean"` BlockExploits bool `json:"block_exploits" gorm:"column:block_exploits" filter:"block_exploits,boolean"`
CachingEnabled bool `json:"caching_enabled" db:"caching_enabled" filter:"caching_enabled,boolean"` AllowWebsocketUpgrade bool `json:"allow_websocket_upgrade" gorm:"column:allow_websocket_upgrade" filter:"allow_websocket_upgrade,boolean"`
BlockExploits bool `json:"block_exploits" db:"block_exploits" filter:"block_exploits,boolean"` HTTP2Support bool `json:"http2_support" gorm:"column:http2_support" filter:"http2_support,boolean"`
AllowWebsocketUpgrade bool `json:"allow_websocket_upgrade" db:"allow_websocket_upgrade" filter:"allow_websocket_upgrade,boolean"` HSTSEnabled bool `json:"hsts_enabled" gorm:"column:hsts_enabled" filter:"hsts_enabled,boolean"`
HTTP2Support bool `json:"http2_support" db:"http2_support" filter:"http2_support,boolean"` HSTSSubdomains bool `json:"hsts_subdomains" gorm:"column:hsts_subdomains" filter:"hsts_subdomains,boolean"`
HSTSEnabled bool `json:"hsts_enabled" db:"hsts_enabled" filter:"hsts_enabled,boolean"` Paths string `json:"paths" gorm:"column:paths" filter:"paths,string"`
HSTSSubdomains bool `json:"hsts_subdomains" db:"hsts_subdomains" filter:"hsts_subdomains,boolean"` AdvancedConfig string `json:"advanced_config" gorm:"column:advanced_config" filter:"advanced_config,string"`
Paths string `json:"paths" db:"paths" filter:"paths,string"` Status string `json:"status" gorm:"column:status" filter:"status,string"`
AdvancedConfig string `json:"advanced_config" db:"advanced_config" filter:"advanced_config,string"` ErrorMessage string `json:"error_message" gorm:"column:error_message" filter:"error_message,string"`
Status string `json:"status" db:"status" filter:"status,string"` IsDisabled bool `json:"is_disabled" gorm:"column:is_disabled" filter:"is_disabled,boolean"`
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"`
// Expansions // Expansions
Certificate *certificate.Model `json:"certificate,omitempty"` Certificate *certificate.Model `json:"certificate,omitempty" gorm:"-"`
NginxTemplate *nginxtemplate.Model `json:"nginx_template,omitempty"` NginxTemplate *nginxtemplate.Model `json:"nginx_template,omitempty" gorm:"-"`
User *user.Model `json:"user,omitempty"` User *user.Model `json:"user,omitempty" gorm:"-"`
Upstream *upstream.Model `json:"upstream,omitempty"` Upstream *upstream.Model `json:"upstream,omitempty" gorm:"-"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "host"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id uint) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id, 0} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
}
// 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
} }
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save(skipConfiguration bool) error { func (m *Model) Save(skipConfiguration bool) error {
var err error
if m.UserID == 0 { if m.UserID == 0 {
return eris.Errorf("User ID must be specified") return eris.Errorf("User ID must be specified")
} }
@ -97,23 +80,20 @@ func (m *Model) Save(skipConfiguration bool) error {
m.Status = status.StatusReady m.Status = status.StatusReady
} }
if m.ID == 0 { db := database.GetDB()
m.ID, err = create(m) result := db.Save(m)
} else { return result.Error
err = update(m)
}
return err
} }
// Delete will mark a host as deleted // Delete will mark row as deleted
func (m *Model) Delete() bool { func (m *Model) Delete() bool {
m.Touch(false) if m.ID == 0 {
m.IsDeleted = true // Can't delete a new object
if err := m.Save(false); err != nil {
return false return false
} }
return true db := database.GetDB()
result := db.Delete(m)
return result.Error == nil
} }
// Expand will fill in more properties // Expand will fill in more properties
@ -160,8 +140,8 @@ func (m *Model) GetTemplate() Template {
t := Template{ t := Template{
ID: m.ID, ID: m.ID,
CreatedOn: m.CreatedOn.Time.String(), CreatedAt: fmt.Sprintf("%d", m.CreatedAt), // todo: format as nice string
ModifiedOn: m.ModifiedOn.Time.String(), UpdatedAt: fmt.Sprintf("%d", m.UpdatedAt), // todo: format as nice string
UserID: m.UserID, UserID: m.UserID,
Type: m.Type, Type: m.Type,
NginxTemplateID: m.NginxTemplateID, NginxTemplateID: m.NginxTemplateID,

View File

@ -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"`
}

View File

@ -4,20 +4,20 @@ import "npm/internal/entity/upstream"
// Template is the model given to the template parser, converted from the Model // Template is the model given to the template parser, converted from the Model
type Template struct { type Template struct {
ID int ID uint
CreatedOn string CreatedAt string
ModifiedOn string UpdatedAt string
UserID int UserID uint
Type string Type string
NginxTemplateID int NginxTemplateID uint
ProxyScheme string ProxyScheme string
ProxyHost string ProxyHost string
ProxyPort int ProxyPort int
ListenInterface string ListenInterface string
DomainNames []string DomainNames []string
UpstreamID int UpstreamID uint
CertificateID int CertificateID uint
AccessListID int AccessListID uint
SSLForced bool SSLForced bool
CachingEnabled bool CachingEnabled bool
BlockExploits bool BlockExploits bool

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -1,123 +1,41 @@
package nginxtemplate package nginxtemplate
import ( import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a Host by ID // GetByID finds a Host by ID
func GetByID(id int) (Model, error) { func GetByID(id uint) (Model, error) {
var m Model var m Model
err := m.LoadByID(id) err := m.LoadByID(id)
return m, err 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 // List will return a list of hosts
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "created_on", Field: "created_on",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
} }
result = ListResponse{ result = entity.ListResponse{
Items: items, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, Limit: pageInfo.Limit,

View File

@ -1,75 +1,51 @@
package nginxtemplate package nginxtemplate
import ( import (
"fmt"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/types" "npm/internal/entity"
"github.com/rotisserie/eris" "github.com/rotisserie/eris"
) )
const ( // Model is the model
tableName = "nginx_template"
)
// Model is the user model
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` UserID int `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"`
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` Type string `json:"type" gorm:"column:type" filter:"type,string"`
Name string `json:"name" db:"name" filter:"name,string"` Template string `json:"template" gorm:"column:template" filter:"template,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"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "nginx_template"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id uint) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id, 0} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
}
// 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
} }
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save() error { func (m *Model) Save() error {
var err error
if m.UserID == 0 { if m.UserID == 0 {
return eris.Errorf("User ID must be specified") return eris.Errorf("User ID must be specified")
} }
if m.ID == 0 { db := database.GetDB()
m.ID, err = Create(m) result := db.Save(m)
} else { return result.Error
err = Update(m)
}
return err
} }
// Delete will mark a template as deleted // Delete will mark row as deleted
func (m *Model) Delete() bool { func (m *Model) Delete() bool {
m.Touch(false) if m.ID == 0 {
m.IsDeleted = true // Can't delete a new object
if err := m.Save(); err != nil {
return false return false
} }
return true db := database.GetDB()
result := db.Delete(m)
return result.Error == nil
} }

View File

@ -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"`
}

View File

@ -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, ", ")
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -1,16 +1,10 @@
package setting package setting
import ( import (
"database/sql" "npm/internal/config"
"fmt"
"npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger" "npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a setting by ID // GetByID finds a setting by ID
@ -27,95 +21,30 @@ func GetByName(name string) (Model, error) {
return m, err 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 // List will return a list of settings
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "name", Field: "name",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%+v", queryErr)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Debug("%+v", err)
return result, err
} }
result = ListResponse{ result = entity.ListResponse{
Items: items, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, Limit: pageInfo.Limit,
@ -126,3 +55,16 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error)
return result, nil 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"
}
}

View File

@ -1,73 +1,50 @@
package setting package setting
import ( import (
"fmt"
"strings" "strings"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/types" "npm/internal/entity"
"gorm.io/datatypes"
) )
const ( // Model is the model
tableName = "setting"
)
// Model is the user model
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` Description string `json:"description" gorm:"column:description" filter:"description,string"`
Name string `json:"name" db:"name" filter:"name,string"` Value datatypes.JSON `json:"value" gorm:"column:value"`
Description string `json:"description" db:"description" filter:"description,string"`
Value types.JSONB `json:"value" db:"value"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "setting"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id int) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE `id` = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
} }
// LoadByName will load from a Name // LoadByName will load from a Name
func (m *Model) LoadByName(name string) error { func (m *Model) LoadByName(name string) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE LOWER(`name`) = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{strings.TrimSpace(strings.ToLower(name))} result := db.Where("name = ?", strings.ToLower(name)).First(&m)
return m.getByQuery(query, params) return result.Error
}
// 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
} }
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save() error { func (m *Model) Save() error {
var err error
// ensure name is trimmed of whitespace // ensure name is trimmed of whitespace
m.Name = strings.TrimSpace(m.Name) m.Name = strings.TrimSpace(m.Name)
if m.ID == 0 { db := database.GetDB()
m.ID, err = Create(m) if result := db.Save(m); result.Error != nil {
} else { return result.Error
err = Update(m)
} }
// Reapply settings
if err == nil {
ApplySettings() ApplySettings()
} return nil
return err
} }

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -1,129 +1,41 @@
package stream package stream
import ( import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a auth by ID // GetByID finds a auth by ID
func GetByID(id int) (Model, error) { func GetByID(id uint) (Model, error) {
var m Model var m Model
err := m.LoadByID(id) err := m.LoadByID(id)
return m, err 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 // List will return a list of hosts
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "name", Field: "name",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
} }
result = ListResponse{ result = entity.ListResponse{
Items: items, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, Limit: pageInfo.Limit,

View File

@ -1,77 +1,54 @@
package stream package stream
import ( import (
"fmt"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity"
"npm/internal/types" "npm/internal/types"
"github.com/rotisserie/eris" "github.com/rotisserie/eris"
) )
const ( // Model is the model
tableName = "stream"
)
// Model is the user model
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` ExpiresOn types.DBDate `json:"expires_on" gorm:"column:expires_on" filter:"expires_on,integer"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` UserID int `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
ExpiresOn types.DBDate `json:"expires_on" db:"expires_on" filter:"expires_on,integer"` Provider string `json:"provider" gorm:"column:provider" filter:"provider,string"`
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"`
Provider string `json:"provider" db:"provider" filter:"provider,string"` DomainNames types.JSONB `json:"domain_names" gorm:"column:domain_names" filter:"domain_names,string"`
Name string `json:"name" db:"name" filter:"name,string"` Meta types.JSONB `json:"-" gorm:"column:meta"`
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"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "stream"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id uint) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id, 0} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
}
// 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
} }
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save() error { func (m *Model) Save() error {
var err error
if m.UserID == 0 { if m.UserID == 0 {
return eris.Errorf("User ID must be specified") return eris.Errorf("User ID must be specified")
} }
if m.ID == 0 { db := database.GetDB()
m.ID, err = Create(m) result := db.Save(m)
} else { return result.Error
err = Update(m)
}
return err
} }
// Delete will mark a host as deleted // Delete will mark row as deleted
func (m *Model) Delete() bool { func (m *Model) Delete() bool {
m.Touch(false) if m.ID == 0 {
m.IsDeleted = true // Can't delete a new object
if err := m.Save(); err != nil {
return false return false
} }
return true db := database.GetDB()
result := db.Delete(m)
return result.Error == nil
} }

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -1,147 +1,38 @@
package upstream package upstream
import ( import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a Upstream by ID // GetByID finds a Upstream by ID
func GetByID(id int) (Model, error) { func GetByID(id uint) (Model, error) {
var m Model var m Model
err := m.LoadByID(id) err := m.LoadByID(id)
return m, err 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 // List will return a list of Upstreams
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "name", Field: "name",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
} }
// Expand to get servers, at a minimum // 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) items[idx].Expand(expand)
} }
result = ListResponse{ result = entity.ListResponse{
Items: items, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, Limit: pageInfo.Limit,

View File

@ -1,79 +1,55 @@
package upstream package upstream
import ( import (
"fmt"
"strings" "strings"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity"
"npm/internal/entity/nginxtemplate" "npm/internal/entity/nginxtemplate"
"npm/internal/entity/upstreamserver" "npm/internal/entity/upstreamserver"
"npm/internal/entity/user" "npm/internal/entity/user"
"npm/internal/status" "npm/internal/status"
"npm/internal/types"
"npm/internal/util" "npm/internal/util"
"github.com/rotisserie/eris" "github.com/rotisserie/eris"
) )
const ( // Model is the model
tableName = "upstream"
)
// Model is the Upstream model
// See: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream // See: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"`
UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` NginxTemplateID uint `json:"nginx_template_id" gorm:"column:nginx_template_id" filter:"nginx_template_id,integer"`
Name string `json:"name" db:"name" filter:"name,string"` IPHash bool `json:"ip_hash" gorm:"column:ip_hash" filter:"ip_hash,boolean"`
NginxTemplateID int `json:"nginx_template_id" db:"nginx_template_id" filter:"nginx_template_id,integer"` NTLM bool `json:"ntlm" gorm:"column:ntlm" filter:"ntlm,boolean"`
IPHash bool `json:"ip_hash" db:"ip_hash" filter:"ip_hash,boolean"` Keepalive int `json:"keepalive" gorm:"column:keepalive" filter:"keepalive,integer"`
NTLM bool `json:"ntlm" db:"ntlm" filter:"ntlm,boolean"` KeepaliveRequests int `json:"keepalive_requests" gorm:"column:keepalive_requests" filter:"keepalive_requests,integer"`
Keepalive int `json:"keepalive" db:"keepalive" filter:"keepalive,integer"` KeepaliveTime string `json:"keepalive_time" gorm:"column:keepalive_time" filter:"keepalive_time,string"`
KeepaliveRequests int `json:"keepalive_requests" db:"keepalive_requests" filter:"keepalive_requests,integer"` KeepaliveTimeout string `json:"keepalive_timeout" gorm:"column:keepalive_timeout" filter:"keepalive_timeout,string"`
KeepaliveTime string `json:"keepalive_time" db:"keepalive_time" filter:"keepalive_time,string"` AdvancedConfig string `json:"advanced_config" gorm:"column:advanced_config" filter:"advanced_config,string"`
KeepaliveTimeout string `json:"keepalive_timeout" db:"keepalive_timeout" filter:"keepalive_timeout,string"` Status string `json:"status" gorm:"column:status" filter:"status,string"`
AdvancedConfig string `json:"advanced_config" db:"advanced_config" filter:"advanced_config,string"` ErrorMessage string `json:"error_message" gorm:"column:error_message" filter:"error_message,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"`
// Expansions // Expansions
Servers []upstreamserver.Model `json:"servers"` Servers []upstreamserver.Model `json:"servers" gorm:"-"`
NginxTemplate *nginxtemplate.Model `json:"nginx_template,omitempty"` NginxTemplate *nginxtemplate.Model `json:"nginx_template,omitempty" gorm:"-"`
User *user.Model `json:"user,omitempty"` User *user.Model `json:"user,omitempty" gorm:"-"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "upstream"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id uint) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id, 0} result := db.First(&m, id)
err := m.getByQuery(query, params) return result.Error
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
} }
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save(skipConfiguration bool) error { func (m *Model) Save(skipConfiguration bool) error {
var err error
if m.UserID == 0 { if m.UserID == 0 {
return eris.Errorf("User ID must be specified") return eris.Errorf("User ID must be specified")
} }
@ -86,14 +62,13 @@ func (m *Model) Save(skipConfiguration bool) error {
m.Status = status.StatusReady m.Status = status.StatusReady
} }
if m.ID == 0 { db := database.GetDB()
m.ID, err = create(m) if result := db.Save(m); result.Error != nil {
} else { return result.Error
err = update(m)
} }
// Save Servers // Save Servers
if err == nil { var err error
for idx := range m.Servers { for idx := range m.Servers {
// Continue if previous iteration didn't cause an error // Continue if previous iteration didn't cause an error
if err == nil { if err == nil {
@ -101,19 +76,19 @@ func (m *Model) Save(skipConfiguration bool) error {
err = m.Servers[idx].Save() err = m.Servers[idx].Save()
} }
} }
}
return err return err
} }
// Delete will mark a upstream as deleted // Delete will mark row as deleted
func (m *Model) Delete() bool { func (m *Model) Delete() bool {
m.Touch(false) if m.ID == 0 {
m.IsDeleted = true // Can't delete a new object
if err := m.Save(false); err != nil {
return false return false
} }
return true db := database.GetDB()
result := db.Delete(m)
return result.Error == nil
} }
// Expand will fill in more properties // Expand will fill in more properties

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -1,16 +1,9 @@
package upstreamserver package upstreamserver
import ( import (
"database/sql"
"fmt"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a Upstream Server by ID // GetByID finds a Upstream Server by ID
@ -21,128 +14,37 @@ func GetByID(id int) (Model, error) {
} }
// GetByUpstreamID finds all servers in the upstream // GetByUpstreamID finds all servers in the upstream
func GetByUpstreamID(upstreamID int) ([]Model, error) { func GetByUpstreamID(upstreamID uint) ([]Model, error) {
items := make([]Model, 0) items := make([]Model, 0)
query := `SELECT * FROM ` + fmt.Sprintf("`%s`", tableName) + ` WHERE upstream_id = ? ORDER BY server` db := database.GetDB()
db := database.GetInstance() result := db.Where("upstream_id = ?", upstreamID).Order("server ASC").Find(&items)
err := db.Select(&items, query, upstreamID) return items, result.Error
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
} }
// List will return a list of Upstreams // List will return a list of Upstreams
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "server", Field: "server",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
} }
result = ListResponse{ result = entity.ListResponse{
Items: items, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, Limit: pageInfo.Limit,

View File

@ -1,78 +1,48 @@
package upstreamserver package upstreamserver
import ( import (
"fmt"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/types" "npm/internal/entity"
"github.com/rotisserie/eris"
) )
const ( // Model is the model
tableName = "upstream_server"
)
// Model is the upstream model
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` UpstreamID uint `json:"upstream_id" gorm:"column:upstream_id" filter:"upstream_id,integer"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` Server string `json:"server" gorm:"column:server" filter:"server,string"`
UpstreamID int `json:"upstream_id" db:"upstream_id" filter:"upstream_id,integer"` Weight int `json:"weight" gorm:"column:weight" filter:"weight,integer"`
Server string `json:"server" db:"server" filter:"server,string"` MaxConns int `json:"max_conns" gorm:"column:max_conns" filter:"max_conns,integer"`
Weight int `json:"weight" db:"weight" filter:"weight,integer"` MaxFails int `json:"max_fails" gorm:"column:max_fails" filter:"max_fails,integer"`
MaxConns int `json:"max_conns" db:"max_conns" filter:"max_conns,integer"` FailTimeout int `json:"fail_timeout" gorm:"column:fail_timeout" filter:"fail_timeout,integer"`
MaxFails int `json:"max_fails" db:"max_fails" filter:"max_fails,integer"` Backup bool `json:"backup" gorm:"column:is_backup" filter:"backup,boolean"`
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"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
return database.GetByQuery(m, query, params) func (Model) TableName() string {
return "upstream_server"
} }
// LoadByID will load from an ID // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id int) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id, 0} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
}
// 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
} }
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save() error { func (m *Model) Save() error {
var err error db := database.GetDB()
result := db.Save(m)
if m.UpstreamID == 0 { return result.Error
return eris.Errorf("Upstream ID must be specified")
}
if m.ID == 0 {
m.ID, err = create(m)
} else {
err = update(m)
}
return err
} }
// Delete will mark a upstream as deleted // Delete will mark row as deleted
func (m *Model) Delete() bool { func (m *Model) Delete() bool {
m.Touch(false) if m.ID == 0 {
m.IsDeleted = true // Can't delete a new object
if err := m.Save(); err != nil {
return false return false
} }
return true db := database.GetDB()
result := db.Delete(m)
return result.Error == nil
} }

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -1,20 +1,14 @@
package user package user
import ( import (
"database/sql"
"fmt"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity" "npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger" "npm/internal/logger"
"npm/internal/model" "npm/internal/model"
"github.com/rotisserie/eris"
) )
// GetByID finds a user by ID // GetByID finds a user by ID
func GetByID(id int) (Model, error) { func GetByID(id uint) (Model, error) {
var m Model var m Model
err := m.LoadByID(id) err := m.LoadByID(id)
return m, err return m, err
@ -27,140 +21,38 @@ func GetByEmail(email string) (Model, error) {
return m, err 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 // IsEnabled is used by middleware to ensure the user is still enabled
// returns (userExist, isEnabled) // returns (userExist, isEnabled)
func IsEnabled(userID int) (bool, bool) { func IsEnabled(userID uint) (bool, bool) {
// nolint: gosec var user Model
query := `SELECT is_disabled FROM ` + fmt.Sprintf("`%s`", tableName) + ` WHERE id = ? AND is_deleted = ?` db := database.GetDB()
disabled := true if result := db.First(&user, userID); result.Error != nil {
db := database.GetInstance()
err := db.QueryRowx(query, userID, 0).Scan(&disabled)
if err == sql.ErrNoRows {
return false, false return false, false
} else if err != nil {
logger.Error("QueryError", err)
} }
return true, !user.IsDisabled
return true, !disabled
} }
// List will return a list of users // List will return a list of users
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) { func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) {
var result ListResponse var result entity.ListResponse
var exampleModel Model
defaultSort := model.Sort{ defaultSort := model.Sort{
Field: "name", Field: "name",
Direction: "ASC", Direction: "ASC",
} }
db := database.GetInstance() dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
/*
filters = append(filters, model.Filter{
Field: "is_system",
Modifier: "equals",
Value: []string{"0"},
})
*/
// Get count of items in this search // Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true) var totalRows int64
countRow := db.QueryRowx(query, params...) if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
var totalRows int return result, res.Error
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("Query: %s -- %+v", query, params)
return result, queryErr
} }
// Get rows // Get rows
items := make([]Model, 0) items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false) if res := dbo.Find(&items); res.Error != nil {
err := db.Select(&items, query, params...) return result, res.Error
if err != nil {
logger.Debug("Query: %s -- %+v", query, params)
return result, err
} }
for idx := range items { 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, Items: items,
Total: totalRows, Total: totalRows,
Limit: pageInfo.Limit, 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. // DeleteAll will do just that, and should only be used for testing purposes.
func DeleteAll() error { func DeleteAll() error {
db := database.GetInstance() db := database.GetDB()
_, err := db.Exec(fmt.Sprintf("DELETE FROM `%s`", tableName)) result := db.Exec("DELETE FROM users")
return err return result.Error
} }
// GetCapabilities gets capabilities for a user // GetCapabilities gets capabilities for a user
func GetCapabilities(userID int) ([]string, error) { func GetCapabilities(userID uint) ([]string, error) {
var capabilities []string capabilities := make([]string, 0)
db := database.GetInstance() var hasCapabilities []UserHasCapabilityModel
if db == nil { db := database.GetDB()
return []string{}, errors.ErrDatabaseUnavailable if result := db.Where("user_id = ?", userID).Find(&hasCapabilities); result.Error != nil {
return nil, result.Error
} }
for _, obj := range hasCapabilities {
query := `SELECT c.name FROM "user_has_capability" h capabilities = append(capabilities, obj.CapabilityName)
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
} }
// 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 return capabilities, nil
} }

View File

@ -1,62 +1,67 @@
package user package user
import ( import (
"fmt"
"strings" "strings"
"time"
"npm/internal/database" "npm/internal/database"
"npm/internal/entity"
"npm/internal/entity/auth" "npm/internal/entity/auth"
"npm/internal/errors" "npm/internal/errors"
"npm/internal/logger"
"npm/internal/types"
"npm/internal/util" "npm/internal/util"
"github.com/drexedam/gravatar" "github.com/drexedam/gravatar"
"github.com/rotisserie/eris" "github.com/rotisserie/eris"
) )
const ( // Model is the model
tableName = "user"
)
// Model is the user model
type Model struct { type Model struct {
ID int `json:"id" db:"id" filter:"id,integer"` entity.ModelBase
Name string `json:"name" db:"name" filter:"name,string"` Name string `json:"name" gorm:"column:name" filter:"name,string"`
Nickname string `json:"nickname" db:"nickname" filter:"nickname,string"` Nickname string `json:"nickname" gorm:"column:nickname" filter:"nickname,string"`
Email string `json:"email" db:"email" filter:"email,email"` Email string `json:"email" gorm:"column:email" filter:"email,email"`
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` IsDisabled bool `json:"is_disabled" gorm:"column:is_disabled" filter:"is_disabled,boolean"`
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` IsSystem bool `json:"is_system,omitempty" gorm:"column:is_system"`
GravatarURL string `json:"gravatar_url"` // Other
IsDisabled bool `json:"is_disabled" db:"is_disabled" filter:"is_disabled,boolean"` GravatarURL string `json:"gravatar_url" gorm:"-"`
IsSystem bool `json:"is_system,omitempty" db:"is_system"`
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
// Expansions // Expansions
Auth *auth.Model `json:"auth,omitempty" db:"-"` Auth *auth.Model `json:"auth,omitempty" gorm:"-"`
Capabilities []string `json:"capabilities,omitempty"` Capabilities []string `json:"capabilities,omitempty" gorm:"-"`
} }
func (m *Model) getByQuery(query string, params []interface{}) error { // TableName overrides the table name used by gorm
err := database.GetByQuery(m, query, params) func (Model) TableName() string {
m.generateGravatar() return "user"
return err }
// 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 // LoadByID will load from an ID
func (m *Model) LoadByID(id int) error { func (m *Model) LoadByID(id uint) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{id, false} result := db.First(&m, id)
return m.getByQuery(query, params) return result.Error
} }
// LoadByEmail will load from an Email // LoadByEmail will load from an Email
func (m *Model) LoadByEmail(email string) error { func (m *Model) LoadByEmail(email string) error {
query := fmt.Sprintf("SELECT * FROM `%s` WHERE email = ? AND is_deleted = ? AND is_system = ? LIMIT 1", tableName) db := database.GetDB()
params := []interface{}{strings.TrimSpace(strings.ToLower(email)), false, false} result := db.
return m.getByQuery(query, params) Where("email = ?", strings.TrimSpace(strings.ToLower(email))).
Where("is_system = ?", false).
First(&m)
return result.Error
} }
/*
// Touch will update model's timestamp(s) // Touch will update model's timestamp(s)
func (m *Model) Touch(created bool) { func (m *Model) Touch(created bool) {
var d types.DBDate var d types.DBDate
@ -67,34 +72,31 @@ func (m *Model) Touch(created bool) {
m.ModifiedOn = d m.ModifiedOn = d
m.generateGravatar() m.generateGravatar()
} }
*/
// Save will save this model to the DB // Save will save this model to the DB
func (m *Model) Save() error { func (m *Model) Save() error {
var err error
// Ensure email is nice // Ensure email is nice
m.Email = strings.TrimSpace(strings.ToLower(m.Email)) m.Email = strings.TrimSpace(strings.ToLower(m.Email))
if m.IsSystem { if m.IsSystem {
return errors.ErrSystemUserReadonly return errors.ErrSystemUserReadonly
} }
if m.ID == 0 { db := database.GetDB()
m.ID, err = Create(m) // todo: touch? not sure that save does this or not?
} else { result := db.Save(m)
err = Update(m) return result.Error
}
return err
} }
// Delete will mark a user as deleted // Delete will mark a user as deleted
func (m *Model) Delete() bool { func (m *Model) Delete() bool {
m.Touch(false) if m.ID == 0 {
m.IsDeleted = true // Can't delete a new object
if err := m.Save(); err != nil {
return false 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 // 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") return eris.Errorf("Cannot set permissions without first saving the User")
} }
db := database.GetInstance() db := database.GetDB()
// Wipe out previous permissions // Wipe out previous permissions
query := `DELETE FROM "user_has_capability" WHERE "user_id" = ?` if result := db.Where("user_id = ?", m.ID).Delete(&UserHasCapabilityModel{}); result.Error != nil {
if _, err := db.Exec(query, m.ID); err != nil { return result.Error
logger.Debug("QUERY: %v -- %v", query, m.ID)
return err
} }
if len(permissions) > 0 { if len(permissions) > 0 {
// Add new permissions // Add new permissions
objs := []*UserHasCapabilityModel{}
for _, permission := range permissions { for _, permission := range permissions {
query = `INSERT INTO "user_has_capability" ( objs = append(objs, &UserHasCapabilityModel{UserID: m.ID, CapabilityName: permission})
"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
} }
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") return eris.New("At least 1 capability required for a user")
} }
db := database.GetInstance() db := database.GetDB()
// Get a full list of capabilities // Get a full list of capabilities
var capabilities []string var capabilities []entity.Capability
query := `SELECT "name" from "capability"` if result := db.Find(&capabilities); result.Error != nil {
err := db.Select(&capabilities, query) return result.Error
if err != nil {
return err
} }
// Check that the capabilities defined exist in the db // Check that the capabilities defined exist in the db
for _, cap := range m.Capabilities { for _, cap := range m.Capabilities {
found := false found := false
for _, a := range capabilities { for _, a := range capabilities {
if a == cap { if a.Name == cap {
found = true found = true
} }
} }

View File

@ -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"`
}

View File

@ -12,7 +12,7 @@ import (
// UserJWTClaims is the structure of a JWT for a User // UserJWTClaims is the structure of a JWT for a User
type UserJWTClaims struct { type UserJWTClaims struct {
UserID int `json:"uid"` UserID uint `json:"uid"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
jwt.StandardClaims jwt.StandardClaims
} }

View File

@ -1,14 +1,8 @@
package model package model
import (
"time"
)
// PageInfo is the model used by Api Handlers and passed on to other parts // PageInfo is the model used by Api Handlers and passed on to other parts
// of the application // of the application
type PageInfo struct { type PageInfo struct {
FromDate time.Time `json:"from_date"`
ToDate time.Time `json:"to_date"`
Sort []Sort `json:"sort"` Sort []Sort `json:"sort"`
Offset int `json:"offset"` Offset int `json:"offset"`
Limit int `json:"limit"` Limit int `json:"limit"`

View File

@ -49,9 +49,10 @@ func ConfigureHost(h host.Model) error {
removeHostFiles(h) removeHostFiles(h)
filename := getHostFilename(h, "") filename := getHostFilename(h, "")
if h.IsDeleted { // if h.IsDeleted {
filename = getHostFilename(h, DeletedSuffix) // filename = getHostFilename(h, DeletedSuffix)
} else if h.IsDisabled { // } else if h.IsDisabled {
if h.IsDisabled {
filename = getHostFilename(h, DisabledSuffix) 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 // 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 // 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) filename = getHostFilename(h, ErrorSuffix)
// Clear existing file(s) again // Clear existing file(s) again
removeHostFiles(h) removeHostFiles(h)
@ -108,9 +109,9 @@ func ConfigureUpstream(u upstream.Model) error {
removeUpstreamFiles(u) removeUpstreamFiles(u)
filename := getUpstreamFilename(u, "") filename := getUpstreamFilename(u, "")
if u.IsDeleted { // if u.IsDeleted {
filename = getUpstreamFilename(u, DeletedSuffix) // filename = getUpstreamFilename(u, DeletedSuffix)
} // }
// Write the config to disk // Write the config to disk
err := writeTemplate(filename, u.NginxTemplate.Template, data, "") 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 // Write the .error file, if this isn't a deleted upstream
// as the reload will only fail because of this upstream // as the reload will only fail because of this upstream
if !u.IsDeleted { // if !u.IsDeleted {
filename = getUpstreamFilename(u, ErrorSuffix) filename = getUpstreamFilename(u, ErrorSuffix)
// Clear existing file(s) again // Clear existing file(s) again
removeUpstreamFiles(u) removeUpstreamFiles(u)
// Write the template again, but with an error message at the end of the file // Write the template again, but with an error message at the end of the file
// nolint: errcheck, gosec // nolint: errcheck, gosec
writeTemplate(filename, u.NginxTemplate.Template, data, u.ErrorMessage) writeTemplate(filename, u.NginxTemplate.Template, data, u.ErrorMessage)
} // }
logger.Debug(u.ErrorMessage) logger.Debug(u.ErrorMessage)
} else { } else {
@ -195,9 +196,9 @@ func GetHostConfigContent(h host.Model) (string, error) {
if h.IsDisabled { if h.IsDisabled {
filename = getHostFilename(h, DisabledSuffix) filename = getHostFilename(h, DisabledSuffix)
} }
if h.IsDeleted { // if h.IsDeleted {
filename = getHostFilename(h, DeletedSuffix) // filename = getHostFilename(h, DeletedSuffix)
} // }
// nolint: gosec // nolint: gosec
cnt, err := os.ReadFile(filename) cnt, err := os.ReadFile(filename)
@ -213,9 +214,9 @@ func GetUpstreamConfigContent(u upstream.Model) (string, error) {
if u.ErrorMessage != "" { if u.ErrorMessage != "" {
filename = getUpstreamFilename(u, ErrorSuffix) filename = getUpstreamFilename(u, ErrorSuffix)
} }
if u.IsDeleted { // if u.IsDeleted {
filename = getUpstreamFilename(u, DeletedSuffix) // filename = getUpstreamFilename(u, DeletedSuffix)
} // }
// nolint: gosec // nolint: gosec
cnt, err := os.ReadFile(filename) cnt, err := os.ReadFile(filename)

View File

@ -3,6 +3,7 @@ package nginx
import ( import (
"testing" "testing"
"npm/internal/entity"
"npm/internal/entity/certificate" "npm/internal/entity/certificate"
"npm/internal/entity/host" "npm/internal/entity/host"
@ -45,7 +46,9 @@ server {
IsDisabled: false, IsDisabled: false,
}, },
cert: certificate.Model{ cert: certificate.Model{
ModelBase: entity.ModelBase{
ID: 77, ID: 77,
},
Status: certificate.StatusProvided, Status: certificate.StatusProvided,
Type: certificate.TypeHTTP, Type: certificate.TypeHTTP,
CertificateAuthorityID: 99, CertificateAuthorityID: 99,
@ -61,7 +64,9 @@ server {
IsDisabled: false, IsDisabled: false,
}, },
cert: certificate.Model{ cert: certificate.Model{
ModelBase: entity.ModelBase{
ID: 66, ID: 66,
},
Status: certificate.StatusProvided, Status: certificate.StatusProvided,
Type: certificate.TypeCustom, Type: certificate.TypeCustom,
}, },

View File

@ -58,6 +58,7 @@ build_backend() {
-w '/app/backend' \ -w '/app/backend' \
"${IMAGE}" \ "${IMAGE}" \
go build \ go build \
-tags 'json1' \
-buildvcs=false \ -buildvcs=false \
-ldflags "-w -s -X main.commit=${BUILD_COMMIT:-notset} -X main.version=${BUILD_VERSION} -X main.sentryDSN=${SENTRY_DSN:-}" \ -ldflags "-w -s -X main.commit=${BUILD_COMMIT:-notset} -X main.version=${BUILD_VERSION} -X main.sentryDSN=${SENTRY_DSN:-}" \
-o "/app/bin/$FILENAME" \ -o "/app/bin/$FILENAME" \

View File

@ -24,6 +24,7 @@ echo -e "${BLUE} ${CYAN}Building binaries for ${YELLOW}${GOARCH} (${TARGETPLA
# server # server
go build \ go build \
-tags 'json1' \
-buildvcs=false \ -buildvcs=false \
-ldflags "-w -s -X main.commit=${BUILD_COMMIT:-notset} -X main.version=${BUILD_VERSION} -X main.sentryDSN=${SENTRY_DSN:-}" \ -ldflags "-w -s -X main.commit=${BUILD_COMMIT:-notset} -X main.version=${BUILD_VERSION} -X main.sentryDSN=${SENTRY_DSN:-}" \
-o "${1:-/dist/server}" \ -o "${1:-/dist/server}" \