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
93 changed files with 1215 additions and 3075 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import (
"net/http"
"strconv"
"strings"
"time"
"npm/internal/api/context"
"npm/internal/model"
@ -19,11 +18,6 @@ func getPageInfoFromRequest(r *http.Request) (model.PageInfo, error) {
var pageInfo model.PageInfo
var err error
pageInfo.FromDate, pageInfo.ToDate, err = getDateRanges(r)
if err != nil {
return pageInfo, err
}
pageInfo.Offset, pageInfo.Limit, err = getPagination(r)
if err != nil {
return pageInfo, err
@ -34,32 +28,6 @@ func getPageInfoFromRequest(r *http.Request) (model.PageInfo, error) {
return pageInfo, nil
}
func getDateRanges(r *http.Request) (time.Time, time.Time, error) {
queryValues := r.URL.Query()
from := queryValues.Get("from")
fromDate := time.Now().AddDate(0, -1, 0) // 1 month ago by default
to := queryValues.Get("to")
toDate := time.Now()
if from != "" {
var fromErr error
fromDate, fromErr = time.Parse(time.RFC3339, from)
if fromErr != nil {
return fromDate, toDate, eris.Errorf("From date is not in correct format: %v", strings.ReplaceAll(time.RFC3339, "Z", "+"))
}
}
if to != "" {
var toErr error
toDate, toErr = time.Parse(time.RFC3339, to)
if toErr != nil {
return fromDate, toDate, eris.Errorf("To date is not in correct format: %v", strings.ReplaceAll(time.RFC3339, "Z", "+"))
}
}
return fromDate, toDate, nil
}
func getSortParameter(r *http.Request) []model.Sort {
var sortFields []model.Sort
@ -132,12 +100,11 @@ func getQueryVarBool(r *http.Request, varName string, required bool, defaultValu
}
*/
func getURLParamInt(r *http.Request, varName string) (int, error) {
func getURLParamInt(r *http.Request, varName string) (uint, error) {
var defaultValue uint = 0
required := true
defaultValue := 0
paramStr := chi.URLParam(r, varName)
var err error
var paramInt int
if paramStr == "" && required {
return 0, eris.Errorf("%v was not supplied in the request", varName)
@ -145,11 +112,13 @@ func getURLParamInt(r *http.Request, varName string) (int, error) {
return defaultValue, nil
}
if paramInt, err = strconv.Atoi(paramStr); err != nil {
// func ParseUint(s string, base int, bitSize int) (n uint64, err error)
paramUint, err := strconv.ParseUint(paramStr, 10, 32)
if err != nil {
return 0, eris.Wrapf(err, "%v is not a valid number", varName)
}
return paramInt, nil
return uint(paramUint), nil
}
func getURLParamString(r *http.Request, varName string) (string, error) {

View File

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

View File

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

View File

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

View File

@ -93,7 +93,7 @@ func RefreshToken() func(http.ResponseWriter, *http.Request) {
// Route: POST /tokens/sse
func NewSSEToken() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value(c.UserIDCtxKey).(int)
userID := r.Context().Value(c.UserIDCtxKey).(uint)
// Find user
userObj, userErr := user.GetByID(userID)

View File

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

View File

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

View File

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

View File

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

View File

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

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

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
import (
"fmt"
"strings"
"npm/internal/errors"
"npm/internal/model"
"npm/internal/util"
)
const (
// DateFormat for DateFormat
DateFormat = "2006-01-02"
// DateTimeFormat for DateTimeFormat
DateTimeFormat = "2006-01-02T15:04:05"
)
// GetByQuery returns a row given a query, populating the model given
func GetByQuery(model interface{}, query string, params []interface{}) error {
db := GetInstance()
if db != nil {
err := db.Get(model, query, params...)
return err
}
return errors.ErrDatabaseUnavailable
}
// BuildOrderBySQL takes a `Sort` slice and constructs a query fragment
func BuildOrderBySQL(columns []string, sort *[]model.Sort) (string, []model.Sort) {
var sortStrings []string
var newSort []model.Sort
for _, sortItem := range *sort {
if util.SliceContainsItem(columns, sortItem.Field) {
sortStrings = append(sortStrings, fmt.Sprintf("`%s` %s", sortItem.Field, sortItem.Direction))
newSort = append(newSort, sortItem)
}
}
if len(sortStrings) > 0 {
return fmt.Sprintf("ORDER BY %s", strings.Join(sortStrings, ", ")), newSort
}
return "", newSort
}

View File

@ -1,203 +1,44 @@
package database
import (
"database/sql"
"fmt"
"io/fs"
"path"
"path/filepath"
"strings"
"sync"
"time"
"net/url"
"npm/embed"
"npm/internal/config"
"npm/internal/logger"
"npm/internal/util"
"github.com/jmoiron/sqlx"
"github.com/rotisserie/eris"
"github.com/amacneil/dbmate/v2/pkg/dbmate"
_ "github.com/amacneil/dbmate/v2/pkg/driver/postgres"
_ "github.com/amacneil/dbmate/v2/pkg/driver/sqlite"
)
// MigrationConfiguration options for the migrator.
type MigrationConfiguration struct {
Table string `json:"table"`
mux sync.Mutex
}
// Default migrator configuration
var mConfiguration = MigrationConfiguration{
Table: "migration",
}
// ConfigureMigrator and will return error if missing required fields.
func ConfigureMigrator(c *MigrationConfiguration) error {
// ensure updates to the config are atomic
mConfiguration.mux.Lock()
defer mConfiguration.mux.Unlock()
if c == nil {
return eris.Errorf("a non nil Configuration is mandatory")
}
if strings.TrimSpace(c.Table) != "" {
mConfiguration.Table = c.Table
}
mConfiguration.Table = c.Table
return nil
}
type afterMigrationComplete func()
// Migrate will perform the migration from start to finish
// Migrate will bring the db up to date
func Migrate(followup afterMigrationComplete) bool {
logger.Info("Migration: Started")
dbURL := config.Configuration.DB.GetDBMateConnectURL()
u, _ := url.Parse(dbURL)
db := dbmate.New(u)
db.FS = embed.MigrationFiles
db.MigrationsDir = []string{fmt.Sprintf("./migrations/%s", config.Configuration.DB.GetDriver())}
// Try to connect to the database sleeping for 15 seconds in between
var db *sqlx.DB
for {
db = GetInstance()
if db == nil {
logger.Warn("Database is unavailable for migration, retrying in 15 seconds")
time.Sleep(15 * time.Second)
} else {
break
}
}
// Check for migration table existence
if !tableExists(db, mConfiguration.Table) {
err := createMigrationTable(db)
if err != nil {
logger.Error("MigratorError", err)
return false
}
logger.Info("Migration: Migration Table created")
}
// DO MIGRATION
migrationCount, migrateErr := performFileMigrations(db)
if migrateErr != nil {
logger.Error("MigratorError", migrateErr)
}
if migrateErr == nil {
logger.Info("Migration: Completed %v migration files", migrationCount)
followup()
return true
}
return false
}
// createMigrationTable performs a query to create the migration table
// with the name specified in the configuration
func createMigrationTable(db *sqlx.DB) error {
logger.Info("Migration: Creating Migration Table: %v", mConfiguration.Table)
// nolint:lll
query := fmt.Sprintf("CREATE TABLE IF NOT EXISTS `%v` (filename TEXT PRIMARY KEY, migrated_on INTEGER NOT NULL DEFAULT 0)", mConfiguration.Table)
_, err := db.Exec(query)
return err
}
// tableExists will check the database for the existence of the specified table.
func tableExists(db *sqlx.DB, tableName string) bool {
query := `SELECT CASE name WHEN $1 THEN true ELSE false END AS found FROM sqlite_master WHERE type='table' AND name = $1`
row := db.QueryRowx(query, tableName)
if row == nil {
logger.Error("MigratorError", eris.Errorf("Cannot check if table exists, no row returned: %v", tableName))
return false
}
var exists *bool
if err := row.Scan(&exists); err != nil {
if err == sql.ErrNoRows {
return false
}
logger.Error("MigratorError", err)
return false
}
return *exists
}
// performFileMigrations will perform the actual migration,
// importing files and updating the database with the rows imported.
func performFileMigrations(db *sqlx.DB) (int, error) {
var importedCount = 0
// Grab a list of previously ran migrations from the database:
previousMigrations, prevErr := getPreviousMigrations(db)
if prevErr != nil {
return importedCount, prevErr
}
// List up the ".sql" files on disk
err := fs.WalkDir(embed.MigrationFiles, ".", func(file string, d fs.DirEntry, err error) error {
if !d.IsDir() {
shortFile := filepath.Base(file)
// Check if this file already exists in the previous migrations
// and if so, ignore it
if util.SliceContainsItem(previousMigrations, shortFile) {
return nil
}
logger.Info("Migration: Importing %v", shortFile)
sqlContents, ioErr := embed.MigrationFiles.ReadFile(path.Clean(file))
if ioErr != nil {
return ioErr
}
sqlString := string(sqlContents)
tx := db.MustBegin()
if _, execErr := tx.Exec(sqlString); execErr != nil {
return execErr
}
if commitErr := tx.Commit(); commitErr != nil {
return commitErr
}
if markErr := markMigrationSuccessful(db, shortFile); markErr != nil {
return markErr
}
importedCount++
}
return nil
})
return importedCount, err
}
// getPreviousMigrations will query the migration table for names
// of migrations we can ignore because they should have already
// been imported
func getPreviousMigrations(db *sqlx.DB) ([]string, error) {
var existingMigrations []string
// nolint:gosec
query := fmt.Sprintf("SELECT filename FROM `%v` ORDER BY filename", mConfiguration.Table)
rows, err := db.Queryx(query)
migrations, err := db.FindMigrations()
if err != nil {
if err == sql.ErrNoRows {
return existingMigrations, nil
}
return existingMigrations, err
logger.Error("MigrationError", err)
return false
}
for rows.Next() {
var filename *string
err := rows.Scan(&filename)
if err != nil {
return existingMigrations, err
}
existingMigrations = append(existingMigrations, *filename)
for _, m := range migrations {
logger.Debug("%s: %s", m.Version, m.FilePath)
}
return existingMigrations, nil
}
err = db.CreateAndMigrate()
if err != nil {
logger.Error("MigrationError", err)
return false
}
// markMigrationSuccessful will add a row to the migration table
func markMigrationSuccessful(db *sqlx.DB, filename string) error {
// nolint:gosec
query := fmt.Sprintf("INSERT INTO `%v` (filename) VALUES ($1)", mConfiguration.Table)
_, err := db.Exec(query, filename)
return err
followup()
return true
}

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
import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model"
"github.com/rotisserie/eris"
)
// GetByID finds a row by ID
func GetByID(id int) (Model, error) {
func GetByID(id uint) (Model, error) {
var m Model
err := m.LoadByID(id)
return m, err
}
// Create will create a row from this model
func Create(m *Model) (int, error) {
if m.ID != 0 {
return 0, eris.New("Cannot create access list when model already has an ID")
}
m.Touch(true)
db := database.GetInstance()
// nolint: gosec
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
created_on,
modified_on,
user_id,
name,
meta,
is_deleted
) VALUES (
:created_on,
:modified_on,
:user_id,
:name,
:meta,
:is_deleted
)`, m)
if err != nil {
return 0, err
}
last, lastErr := result.LastInsertId()
if lastErr != nil {
return 0, lastErr
}
return int(last), nil
}
// Update will Update a row from this model
func Update(m *Model) error {
if m.ID == 0 {
return eris.New("Cannot update access list when model doesn't have an ID")
}
m.Touch(false)
db := database.GetInstance()
// nolint: gosec
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
created_on = :created_on,
modified_on = :modified_on,
user_id = :user_id,
name = :name,
meta = :meta,
is_deleted = :is_deleted
WHERE id = :id`, m)
return err
}
// List will return a list of access lists
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
var result ListResponse
var exampleModel Model
func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result entity.ListResponse
defaultSort := model.Sort{
Field: "name",
Direction: "ASC",
}
db := database.GetInstance()
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
// Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
countRow := db.QueryRowx(query, params...)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Error("ListAccessListsError", queryErr)
logger.Debug("%s -- %+v", query, params)
return result, queryErr
var totalRows int64
if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
return result, res.Error
}
// Get rows
items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
logger.Error("ListAccessListsError", err)
logger.Debug("%s -- %+v", query, params)
return result, err
if res := dbo.Find(&items); res.Error != nil {
return result, res.Error
}
result = ListResponse{
result = entity.ListResponse{
Items: items,
Total: totalRows,
Limit: pageInfo.Limit,

View File

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

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

View File

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

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

View File

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

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
type Template struct {
ID int
CreatedOn string
ModifiedOn string
ID uint
CreatedAt string
UpdatedAt string
ExpiresOn string
Type string
UserID int
CertificateAuthorityID int
DNSProviderID int
UserID uint
CertificateAuthorityID uint
DNSProviderID uint
Name string
DomainNames []string
Status string

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
import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model"
"github.com/rotisserie/eris"
)
// GetByID finds a row by ID
func GetByID(id int) (Model, error) {
func GetByID(id uint) (Model, error) {
var m Model
err := m.LoadByID(id)
return m, err
}
// Create will create a row from this model
func Create(ca *Model) (int, error) {
if ca.ID != 0 {
return 0, eris.New("Cannot create certificate authority when model already has an ID")
}
ca.Touch(true)
db := database.GetInstance()
// nolint: gosec
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
created_on,
modified_on,
name,
acmesh_server,
ca_bundle,
max_domains,
is_wildcard_supported,
is_deleted
) VALUES (
:created_on,
:modified_on,
:name,
:acmesh_server,
:ca_bundle,
:max_domains,
:is_wildcard_supported,
:is_deleted
)`, ca)
if err != nil {
return 0, err
}
last, lastErr := result.LastInsertId()
if lastErr != nil {
return 0, lastErr
}
return int(last), nil
}
// Update will Update a row from this model
func Update(ca *Model) error {
if ca.ID == 0 {
return eris.New("Cannot update certificate authority when model doesn't have an ID")
}
ca.Touch(false)
db := database.GetInstance()
// nolint: gosec
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
created_on = :created_on,
modified_on = :modified_on,
name = :name,
acmesh_server = :acmesh_server,
ca_bundle = :ca_bundle,
max_domains = :max_domains,
is_wildcard_supported = :is_wildcard_supported,
is_deleted = :is_deleted
WHERE id = :id`, ca)
return err
}
// List will return a list of certificates
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
var result ListResponse
var exampleModel Model
func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result entity.ListResponse
defaultSort := model.Sort{
Field: "name",
Direction: "ASC",
}
db := database.GetInstance()
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
// Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
countRow := db.QueryRowx(query, params...)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Error("ListCertificateAuthoritiesError", queryErr)
logger.Debug("%s -- %+v", query, params)
return result, queryErr
var totalRows int64
if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
return result, res.Error
}
// Get rows
items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
logger.Error("ListCertificateAuthoritiesError", err)
logger.Debug("%s -- %+v", query, params)
return result, err
if res := dbo.Find(&items); res.Error != nil {
return result, res.Error
}
result = ListResponse{
result = entity.ListResponse{
Items: items,
Total: totalRows,
Limit: pageInfo.Limit,

View File

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

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
import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model"
"github.com/rotisserie/eris"
)
// GetByID finds a row by ID
func GetByID(id int) (Model, error) {
func GetByID(id uint) (Model, error) {
var m Model
err := m.LoadByID(id)
return m, err
}
// Create will create a row from this model
func Create(provider *Model) (int, error) {
if provider.ID != 0 {
return 0, eris.New("Cannot create dns provider when model already has an ID")
}
provider.Touch(true)
db := database.GetInstance()
// nolint: gosec
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
created_on,
modified_on,
user_id,
name,
acmesh_name,
dns_sleep,
meta,
is_deleted
) VALUES (
:created_on,
:modified_on,
:user_id,
:name,
:acmesh_name,
:dns_sleep,
:meta,
:is_deleted
)`, provider)
if err != nil {
return 0, err
}
last, lastErr := result.LastInsertId()
if lastErr != nil {
return 0, lastErr
}
return int(last), nil
}
// Update will Update a row from this model
func Update(provider *Model) error {
if provider.ID == 0 {
return eris.New("Cannot update dns provider when model doesn't have an ID")
}
provider.Touch(false)
db := database.GetInstance()
// nolint: gosec
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
created_on = :created_on,
modified_on = :modified_on,
user_id = :user_id,
name = :name,
acmesh_name = :acmesh_name,
dns_sleep = :dns_sleep,
meta = :meta,
is_deleted = :is_deleted
WHERE id = :id`, provider)
return err
}
// List will return a list of certificates
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
var result ListResponse
var exampleModel Model
func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result entity.ListResponse
defaultSort := model.Sort{
Field: "name",
Direction: "ASC",
}
db := database.GetInstance()
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
// Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
countRow := db.QueryRowx(query, params...)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Error("ListDnsProvidersError", queryErr)
logger.Debug("%s -- %+v", query, params)
return result, queryErr
var totalRows int64
if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
return result, res.Error
}
// Get rows
items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
logger.Error("ListDnsProvidersError", err)
logger.Debug("%s -- %+v", query, params)
return result, err
if res := dbo.Find(&items); res.Error != nil {
return result, res.Error
}
result = ListResponse{
result = entity.ListResponse{
Items: items,
Total: totalRows,
Limit: pageInfo.Limit,

View File

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

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

View File

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

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
import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model"
"github.com/rotisserie/eris"
)
// GetByID finds a Host by ID
func GetByID(id int) (Model, error) {
func GetByID(id uint) (Model, error) {
var m Model
err := m.LoadByID(id)
return m, err
}
// create will create a Host from this model
func create(host *Model) (int, error) {
if host.ID != 0 {
return 0, eris.New("Cannot create host when model already has an ID")
}
host.Touch(true)
db := database.GetInstance()
// nolint: gosec
result, err := db.NamedExec(`INSERT INTO `+tableName+` (
created_on,
modified_on,
user_id,
type,
nginx_template_id,
listen_interface,
domain_names,
upstream_id,
proxy_scheme,
proxy_host,
proxy_port,
certificate_id,
access_list_id,
ssl_forced,
caching_enabled,
block_exploits,
allow_websocket_upgrade,
http2_support,
hsts_enabled,
hsts_subdomains,
paths,
advanced_config,
status,
error_message,
is_disabled,
is_deleted
) VALUES (
:created_on,
:modified_on,
:user_id,
:type,
:nginx_template_id,
:listen_interface,
:domain_names,
:upstream_id,
:proxy_scheme,
:proxy_host,
:proxy_port,
:certificate_id,
:access_list_id,
:ssl_forced,
:caching_enabled,
:block_exploits,
:allow_websocket_upgrade,
:http2_support,
:hsts_enabled,
:hsts_subdomains,
:paths,
:advanced_config,
:status,
:error_message,
:is_disabled,
:is_deleted
)`, host)
if err != nil {
return 0, err
}
last, lastErr := result.LastInsertId()
if lastErr != nil {
return 0, lastErr
}
logger.Debug("Created Host: %+v", host)
return int(last), nil
}
// update will Update a Host from this model
func update(host *Model) error {
if host.ID == 0 {
return eris.New("Cannot update host when model doesn't have an ID")
}
host.Touch(false)
db := database.GetInstance()
// nolint: gosec
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
created_on = :created_on,
modified_on = :modified_on,
user_id = :user_id,
type = :type,
nginx_template_id = :nginx_template_id,
listen_interface = :listen_interface,
domain_names = :domain_names,
upstream_id = :upstream_id,
proxy_scheme = :proxy_scheme,
proxy_host = :proxy_host,
proxy_port = :proxy_port,
certificate_id = :certificate_id,
access_list_id = :access_list_id,
ssl_forced = :ssl_forced,
caching_enabled = :caching_enabled,
block_exploits = :block_exploits,
allow_websocket_upgrade = :allow_websocket_upgrade,
http2_support = :http2_support,
hsts_enabled = :hsts_enabled,
hsts_subdomains = :hsts_subdomains,
paths = :paths,
advanced_config = :advanced_config,
status = :status,
error_message = :error_message,
is_disabled = :is_disabled,
is_deleted = :is_deleted
WHERE id = :id`, host)
logger.Debug("Updated Host: %+v", host)
return err
}
// List will return a list of hosts
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) {
var result ListResponse
var exampleModel Model
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) {
var result entity.ListResponse
defaultSort := model.Sort{
Field: "domain_names",
Direction: "ASC",
}
db := database.GetInstance()
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
// Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
countRow := db.QueryRowx(query, params...)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
var totalRows int64
if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
return result, res.Error
}
// Get rows
items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
if res := dbo.Find(&items); res.Error != nil {
return result, res.Error
}
if expand != nil {
@ -187,7 +46,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis
}
}
result = ListResponse{
result = entity.ListResponse{
Items: items,
Total: totalRows,
Limit: pageInfo.Limit,
@ -201,32 +60,28 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis
// GetUpstreamUseCount returns the number of hosts that are using
// an upstream, and have not been deleted.
func GetUpstreamUseCount(upstreamID int) int {
db := database.GetInstance()
query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE upstream_id = ? AND is_deleted = ?", tableName)
countRow := db.QueryRowx(query, upstreamID, 0)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s", query)
func GetUpstreamUseCount(upstreamID uint) int64 {
db := database.GetDB()
var count int64
if result := db.Model(&Model{}).Where("upstream_id = ?", upstreamID).Count(&count); result.Error != nil {
logger.Debug("GetUpstreamUseCount Error: %v", result.Error)
return 0
}
return totalRows
return count
}
// GetCertificateUseCount returns the number of hosts that are using
// a certificate, and have not been deleted.
func GetCertificateUseCount(certificateID int) int {
db := database.GetInstance()
query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE certificate_id = ? AND is_deleted = ?", tableName)
countRow := db.QueryRowx(query, certificateID, 0)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s", query)
func GetCertificateUseCount(certificateID uint) int64 {
db := database.GetDB()
var count int64
if result := db.Model(&Model{}).Where("certificate_id = ?", certificateID).Count(&count); result.Error != nil {
logger.Debug("GetUpstreamUseCount Error: %v", result.Error)
return 0
}
return totalRows
return count
}
// AddPendingJobs is intended to be used at startup to add

View File

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

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
type Template struct {
ID int
CreatedOn string
ModifiedOn string
UserID int
ID uint
CreatedAt string
UpdatedAt string
UserID uint
Type string
NginxTemplateID int
NginxTemplateID uint
ProxyScheme string
ProxyHost string
ProxyPort int
ListenInterface string
DomainNames []string
UpstreamID int
CertificateID int
AccessListID int
UpstreamID uint
CertificateID uint
AccessListID uint
SSLForced bool
CachingEnabled bool
BlockExploits bool

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
import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model"
"github.com/rotisserie/eris"
)
// GetByID finds a Host by ID
func GetByID(id int) (Model, error) {
func GetByID(id uint) (Model, error) {
var m Model
err := m.LoadByID(id)
return m, err
}
// Create will create a Host from this model
func Create(host *Model) (int, error) {
if host.ID != 0 {
return 0, eris.New("Cannot create host template when model already has an ID")
}
host.Touch(true)
db := database.GetInstance()
// nolint: gosec
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
created_on,
modified_on,
user_id,
name,
host_type,
template,
is_deleted
) VALUES (
:created_on,
:modified_on,
:user_id,
:name,
:host_type,
:template,
:is_deleted
)`, host)
if err != nil {
return 0, err
}
last, lastErr := result.LastInsertId()
if lastErr != nil {
return 0, lastErr
}
return int(last), nil
}
// Update will Update a Host from this model
func Update(host *Model) error {
if host.ID == 0 {
return eris.New("Cannot update host template when model doesn't have an ID")
}
host.Touch(false)
db := database.GetInstance()
// nolint: gosec
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
created_on = :created_on,
modified_on = :modified_on,
user_id = :user_id,
name = :name,
host_type = :host_type,
template = :template,
is_deleted = :is_deleted
WHERE id = :id`, host)
return err
}
// List will return a list of hosts
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
var result ListResponse
var exampleModel Model
func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result entity.ListResponse
defaultSort := model.Sort{
Field: "created_on",
Direction: "ASC",
}
db := database.GetInstance()
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
// Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
countRow := db.QueryRowx(query, params...)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
var totalRows int64
if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
return result, res.Error
}
// Get rows
items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
if res := dbo.Find(&items); res.Error != nil {
return result, res.Error
}
result = ListResponse{
result = entity.ListResponse{
Items: items,
Total: totalRows,
Limit: pageInfo.Limit,

View File

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

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
import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/config"
"npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model"
"github.com/rotisserie/eris"
)
// GetByID finds a setting by ID
@ -27,95 +21,30 @@ func GetByName(name string) (Model, error) {
return m, err
}
// Create will Create a Setting from this model
func Create(setting *Model) (int, error) {
if setting.ID != 0 {
return 0, eris.New("Cannot create setting when model already has an ID")
}
setting.Touch(true)
db := database.GetInstance()
// nolint: gosec
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
created_on,
modified_on,
name,
value
) VALUES (
:created_on,
:modified_on,
:name,
:value
)`, setting)
if err != nil {
return 0, err
}
last, lastErr := result.LastInsertId()
if lastErr != nil {
return 0, lastErr
}
return int(last), nil
}
// Update will Update a Setting from this model
func Update(setting *Model) error {
if setting.ID == 0 {
return eris.New("Cannot update setting when model doesn't have an ID")
}
setting.Touch(false)
db := database.GetInstance()
// nolint: gosec
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
created_on = :created_on,
modified_on = :modified_on,
name = :name,
value = :value
WHERE id = :id`, setting)
return err
}
// List will return a list of settings
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
var result ListResponse
var exampleModel Model
func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result entity.ListResponse
defaultSort := model.Sort{
Field: "name",
Direction: "ASC",
}
db := database.GetInstance()
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
// Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
countRow := db.QueryRowx(query, params...)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%+v", queryErr)
return result, queryErr
var totalRows int64
if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
return result, res.Error
}
// Get rows
items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
logger.Debug("%+v", err)
return result, err
if res := dbo.Find(&items); res.Error != nil {
return result, res.Error
}
result = ListResponse{
result = entity.ListResponse{
Items: items,
Total: totalRows,
Limit: pageInfo.Limit,
@ -126,3 +55,16 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error)
return result, nil
}
// ApplySettings will load settings from the DB and apply them where required
func ApplySettings() {
logger.Debug("Applying Settings")
// Error-reporting
m, err := GetByName("error-reporting")
if err != nil {
logger.Error("ApplySettingsError", err)
} else {
config.ErrorReporting = m.Value.String() == "true"
}
}

View File

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

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
import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model"
"github.com/rotisserie/eris"
)
// GetByID finds a auth by ID
func GetByID(id int) (Model, error) {
func GetByID(id uint) (Model, error) {
var m Model
err := m.LoadByID(id)
return m, err
}
// Create will create a Auth from this model
func Create(host *Model) (int, error) {
if host.ID != 0 {
return 0, eris.New("Cannot create stream when model already has an ID")
}
host.Touch(true)
db := database.GetInstance()
// nolint: gosec
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
created_on,
modified_on,
user_id,
provider,
name,
domain_names,
expires_on,
meta,
is_deleted
) VALUES (
:created_on,
:modified_on,
:user_id,
:provider,
:name,
:domain_names,
:expires_on,
:meta,
:is_deleted
)`, host)
if err != nil {
return 0, err
}
last, lastErr := result.LastInsertId()
if lastErr != nil {
return 0, lastErr
}
return int(last), nil
}
// Update will Update a Host from this model
func Update(host *Model) error {
if host.ID == 0 {
return eris.New("Cannot update stream when model doesn't have an ID")
}
host.Touch(false)
db := database.GetInstance()
// nolint: gosec
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
created_on = :created_on,
modified_on = :modified_on,
user_id = :user_id,
provider = :provider,
name = :name,
domain_names = :domain_names,
expires_on = :expires_on,
meta = :meta,
is_deleted = :is_deleted
WHERE id = :id`, host)
return err
}
// List will return a list of hosts
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
var result ListResponse
var exampleModel Model
func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result entity.ListResponse
defaultSort := model.Sort{
Field: "name",
Direction: "ASC",
}
db := database.GetInstance()
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
// Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
countRow := db.QueryRowx(query, params...)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
var totalRows int64
if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
return result, res.Error
}
// Get rows
items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
if res := dbo.Find(&items); res.Error != nil {
return result, res.Error
}
result = ListResponse{
result = entity.ListResponse{
Items: items,
Total: totalRows,
Limit: pageInfo.Limit,

View File

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

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
import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model"
"github.com/rotisserie/eris"
)
// GetByID finds a Upstream by ID
func GetByID(id int) (Model, error) {
func GetByID(id uint) (Model, error) {
var m Model
err := m.LoadByID(id)
return m, err
}
// create will create a Upstream from this model
func create(u *Model) (int, error) {
if u.ID != 0 {
return 0, eris.New("Cannot create upstream when model already has an ID")
}
u.Touch(true)
db := database.GetInstance()
// nolint: gosec
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
created_on,
modified_on,
user_id,
name,
nginx_template_id,
ip_hash,
ntlm,
keepalive,
keepalive_requests,
keepalive_time,
keepalive_timeout,
advanced_config,
status,
error_message,
is_deleted
) VALUES (
:created_on,
:modified_on,
:user_id,
:name,
:nginx_template_id,
:ip_hash,
:ntlm,
:keepalive,
:keepalive_requests,
:keepalive_time,
:keepalive_timeout,
:advanced_config,
:status,
:error_message,
:is_deleted
)`, u)
if err != nil {
return 0, err
}
last, lastErr := result.LastInsertId()
if lastErr != nil {
return 0, lastErr
}
logger.Debug("Created Upstream: %+v", u)
return int(last), nil
}
// update will Update a Upstream from this model
func update(u *Model) error {
if u.ID == 0 {
return eris.New("Cannot update upstream when model doesn't have an ID")
}
u.Touch(false)
db := database.GetInstance()
// nolint: gosec
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
created_on = :created_on,
modified_on = :modified_on,
user_id = :user_id,
name = :name,
nginx_template_id = :nginx_template_id,
ip_hash = :ip_hash,
ntlm = :ntlm,
keepalive = :keepalive,
keepalive_requests = :keepalive_requests,
keepalive_time = :keepalive_time,
advanced_config = :advanced_config,
status = :status,
error_message = :error_message,
is_deleted = :is_deleted
WHERE id = :id`, u)
logger.Debug("Updated Upstream: %+v", u)
return err
}
// List will return a list of Upstreams
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) {
var result ListResponse
var exampleModel Model
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) {
var result entity.ListResponse
defaultSort := model.Sort{
Field: "name",
Direction: "ASC",
}
db := database.GetInstance()
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
// Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
countRow := db.QueryRowx(query, params...)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
var totalRows int64
if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
return result, res.Error
}
// Get rows
items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
if res := dbo.Find(&items); res.Error != nil {
return result, res.Error
}
// Expand to get servers, at a minimum
@ -150,7 +41,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis
items[idx].Expand(expand)
}
result = ListResponse{
result = entity.ListResponse{
Items: items,
Total: totalRows,
Limit: pageInfo.Limit,

View File

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

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
import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model"
"github.com/rotisserie/eris"
)
// GetByID finds a Upstream Server by ID
@ -21,128 +14,37 @@ func GetByID(id int) (Model, error) {
}
// GetByUpstreamID finds all servers in the upstream
func GetByUpstreamID(upstreamID int) ([]Model, error) {
func GetByUpstreamID(upstreamID uint) ([]Model, error) {
items := make([]Model, 0)
query := `SELECT * FROM ` + fmt.Sprintf("`%s`", tableName) + ` WHERE upstream_id = ? ORDER BY server`
db := database.GetInstance()
err := db.Select(&items, query, upstreamID)
if err != nil {
logger.Debug("%s -- %d", query, upstreamID)
}
return items, err
}
// create will create a Upstream Server from this model
func create(u *Model) (int, error) {
if u.ID != 0 {
return 0, eris.New("Cannot create upstream server when model already has an ID")
}
u.Touch(true)
db := database.GetInstance()
// nolint: gosec
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
created_on,
modified_on,
upstream_id,
server,
weight,
max_conns,
max_fails,
fail_timeout,
backup,
is_deleted
) VALUES (
:created_on,
:modified_on,
:upstream_id,
:server,
:weight,
:max_conns,
:max_fails,
:fail_timeout,
:backup,
:is_deleted
)`, u)
if err != nil {
return 0, err
}
last, lastErr := result.LastInsertId()
if lastErr != nil {
return 0, lastErr
}
logger.Debug("Created Upstream Server: %+v", u)
return int(last), nil
}
// update will Update a Upstream from this model
func update(u *Model) error {
if u.ID == 0 {
return eris.New("Cannot update upstream server when model doesn't have an ID")
}
u.Touch(false)
db := database.GetInstance()
// nolint: gosec
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
created_on = :created_on,
modified_on = :modified_on,
upstream_id = :upstream_id,
server = :server,
weight = :weight,
max_conns = :max_conns,
max_fails = :max_fails,
fail_timeout = :fail_timeout,
backup = :backup,
is_deleted = :is_deleted
WHERE id = :id`, u)
logger.Debug("Updated Upstream Server: %+v", u)
return err
db := database.GetDB()
result := db.Where("upstream_id = ?", upstreamID).Order("server ASC").Find(&items)
return items, result.Error
}
// List will return a list of Upstreams
func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) {
var result ListResponse
var exampleModel Model
func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, error) {
var result entity.ListResponse
defaultSort := model.Sort{
Field: "server",
Direction: "ASC",
}
db := database.GetInstance()
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
// Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
countRow := db.QueryRowx(query, params...)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("%s -- %+v", query, params)
return result, queryErr
var totalRows int64
if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
return result, res.Error
}
// Get rows
items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
logger.Debug("%s -- %+v", query, params)
return result, err
if res := dbo.Find(&items); res.Error != nil {
return result, res.Error
}
result = ListResponse{
result = entity.ListResponse{
Items: items,
Total: totalRows,
Limit: pageInfo.Limit,

View File

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

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
import (
"database/sql"
"fmt"
"npm/internal/database"
"npm/internal/entity"
"npm/internal/errors"
"npm/internal/logger"
"npm/internal/model"
"github.com/rotisserie/eris"
)
// GetByID finds a user by ID
func GetByID(id int) (Model, error) {
func GetByID(id uint) (Model, error) {
var m Model
err := m.LoadByID(id)
return m, err
@ -27,140 +21,38 @@ func GetByEmail(email string) (Model, error) {
return m, err
}
// Create will create a User from given model
func Create(user *Model) (int, error) {
// We need to ensure that a user can't be created with the same email
// as an existing non-deleted user. Usually you would do this with the
// database schema, but it's a bit more complex because of the is_deleted field.
if user.ID != 0 {
return 0, eris.New("Cannot create user when model already has an ID")
}
// Check if an existing user with this email exists
_, err := GetByEmail(user.Email)
if err == nil {
return 0, errors.ErrDuplicateEmailUser
}
user.Touch(true)
db := database.GetInstance()
// nolint: gosec
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
created_on,
modified_on,
name,
nickname,
email,
is_disabled
) VALUES (
:created_on,
:modified_on,
:name,
:nickname,
:email,
:is_disabled
)`, user)
if err != nil {
return 0, err
}
last, lastErr := result.LastInsertId()
if lastErr != nil {
return 0, lastErr
}
return int(last), nil
}
// Update will Update a User from this model
func Update(user *Model) error {
if user.ID == 0 {
return eris.New("Cannot update user when model doesn't have an ID")
}
// Check that the email address isn't associated with another user
if existingUser, _ := GetByEmail(user.Email); existingUser.ID != 0 && existingUser.ID != user.ID {
return errors.ErrDuplicateEmailUser
}
user.Touch(false)
db := database.GetInstance()
// nolint: gosec
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
created_on = :created_on,
modified_on = :modified_on,
name = :name,
nickname = :nickname,
email = :email,
is_disabled = :is_disabled,
is_deleted = :is_deleted
WHERE id = :id`, user)
return err
}
// IsEnabled is used by middleware to ensure the user is still enabled
// returns (userExist, isEnabled)
func IsEnabled(userID int) (bool, bool) {
// nolint: gosec
query := `SELECT is_disabled FROM ` + fmt.Sprintf("`%s`", tableName) + ` WHERE id = ? AND is_deleted = ?`
disabled := true
db := database.GetInstance()
err := db.QueryRowx(query, userID, 0).Scan(&disabled)
if err == sql.ErrNoRows {
func IsEnabled(userID uint) (bool, bool) {
var user Model
db := database.GetDB()
if result := db.First(&user, userID); result.Error != nil {
return false, false
} else if err != nil {
logger.Error("QueryError", err)
}
return true, !disabled
return true, !user.IsDisabled
}
// List will return a list of users
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) {
var result ListResponse
var exampleModel Model
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (entity.ListResponse, error) {
var result entity.ListResponse
defaultSort := model.Sort{
Field: "name",
Direction: "ASC",
}
db := database.GetInstance()
if db == nil {
return result, errors.ErrDatabaseUnavailable
}
/*
filters = append(filters, model.Filter{
Field: "is_system",
Modifier: "equals",
Value: []string{"0"},
})
*/
dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters)
// Get count of items in this search
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
countRow := db.QueryRowx(query, params...)
var totalRows int
queryErr := countRow.Scan(&totalRows)
if queryErr != nil && queryErr != sql.ErrNoRows {
logger.Debug("Query: %s -- %+v", query, params)
return result, queryErr
var totalRows int64
if res := dbo.Model(&Model{}).Count(&totalRows); res.Error != nil {
return result, res.Error
}
// Get rows
items := make([]Model, 0)
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
err := db.Select(&items, query, params...)
if err != nil {
logger.Debug("Query: %s -- %+v", query, params)
return result, err
if res := dbo.Find(&items); res.Error != nil {
return result, res.Error
}
for idx := range items {
@ -176,7 +68,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis
}
}
result = ListResponse{
result = entity.ListResponse{
Items: items,
Total: totalRows,
Limit: pageInfo.Limit,
@ -190,41 +82,21 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (Lis
// DeleteAll will do just that, and should only be used for testing purposes.
func DeleteAll() error {
db := database.GetInstance()
_, err := db.Exec(fmt.Sprintf("DELETE FROM `%s`", tableName))
return err
db := database.GetDB()
result := db.Exec("DELETE FROM users")
return result.Error
}
// GetCapabilities gets capabilities for a user
func GetCapabilities(userID int) ([]string, error) {
var capabilities []string
db := database.GetInstance()
if db == nil {
return []string{}, errors.ErrDatabaseUnavailable
func GetCapabilities(userID uint) ([]string, error) {
capabilities := make([]string, 0)
var hasCapabilities []UserHasCapabilityModel
db := database.GetDB()
if result := db.Where("user_id = ?", userID).Find(&hasCapabilities); result.Error != nil {
return nil, result.Error
}
query := `SELECT c.name FROM "user_has_capability" h
INNER JOIN "capability" c ON c.id = h.capability_id
WHERE h.user_id = ?`
rows, err := db.Query(query, userID)
if err != nil && err != sql.ErrNoRows {
logger.Debug("QUERY: %v -- %v", query, userID)
return []string{}, err
for _, obj := range hasCapabilities {
capabilities = append(capabilities, obj.CapabilityName)
}
// nolint: errcheck
defer rows.Close()
for rows.Next() {
var name string
err := rows.Scan(&name)
if err != nil {
return []string{}, err
}
capabilities = append(capabilities, name)
}
return capabilities, nil
}

View File

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

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
type UserJWTClaims struct {
UserID int `json:"uid"`
UserID uint `json:"uid"`
Roles []string `json:"roles"`
jwt.StandardClaims
}

View File

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

View File

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

View File

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