mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-06-18 10:06:26 +00:00
Moved v3 code from NginxProxyManager/nginx-proxy-manager-3 to NginxProxyManager/nginx-proxy-manager
This commit is contained in:
46
backend/internal/database/helpers.go
Normal file
46
backend/internal/database/helpers.go
Normal file
@ -0,0 +1,46 @@
|
||||
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
|
||||
}
|
202
backend/internal/database/migrator.go
Normal file
202
backend/internal/database/migrator.go
Normal file
@ -0,0 +1,202 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"npm/embed"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/util"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// 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 fmt.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
|
||||
func Migrate(followup afterMigrationComplete) bool {
|
||||
logger.Info("Migration: Started")
|
||||
|
||||
// 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", fmt.Errorf("Cannot check if table exists, no row returned: %v", tableName))
|
||||
return false
|
||||
}
|
||||
|
||||
var exists *bool
|
||||
if err := row.Scan(&exists); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return false
|
||||
}
|
||||
logger.Error("MigratorError", err)
|
||||
return false
|
||||
}
|
||||
return *exists
|
||||
}
|
||||
|
||||
// performFileMigrations will perform the actual migration,
|
||||
// importing files and updating the database with the rows imported.
|
||||
func performFileMigrations(db *sqlx.DB) (int, error) {
|
||||
var importedCount = 0
|
||||
|
||||
// Grab a list of previously ran migrations from the database:
|
||||
previousMigrations, prevErr := getPreviousMigrations(db)
|
||||
if prevErr != nil {
|
||||
return importedCount, prevErr
|
||||
}
|
||||
|
||||
// List up the ".sql" files on disk
|
||||
err := fs.WalkDir(embed.MigrationFiles, ".", func(file string, d fs.DirEntry, err error) error {
|
||||
if !d.IsDir() {
|
||||
shortFile := filepath.Base(file)
|
||||
|
||||
// Check if this file already exists in the previous migrations
|
||||
// and if so, ignore it
|
||||
if util.SliceContainsItem(previousMigrations, shortFile) {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Info("Migration: Importing %v", shortFile)
|
||||
|
||||
sqlContents, ioErr := embed.MigrationFiles.ReadFile(path.Clean(file))
|
||||
if ioErr != nil {
|
||||
return ioErr
|
||||
}
|
||||
|
||||
sqlString := string(sqlContents)
|
||||
|
||||
tx := db.MustBegin()
|
||||
if _, execErr := tx.Exec(sqlString); execErr != nil {
|
||||
return execErr
|
||||
}
|
||||
if commitErr := tx.Commit(); commitErr != nil {
|
||||
return commitErr
|
||||
}
|
||||
if markErr := markMigrationSuccessful(db, shortFile); markErr != nil {
|
||||
return markErr
|
||||
}
|
||||
|
||||
importedCount++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return importedCount, err
|
||||
}
|
||||
|
||||
// getPreviousMigrations will query the migration table for names
|
||||
// of migrations we can ignore because they should have already
|
||||
// been imported
|
||||
func getPreviousMigrations(db *sqlx.DB) ([]string, error) {
|
||||
var existingMigrations []string
|
||||
// nolint:gosec
|
||||
query := fmt.Sprintf("SELECT filename FROM `%v` ORDER BY filename", mConfiguration.Table)
|
||||
rows, err := db.Queryx(query)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return existingMigrations, nil
|
||||
}
|
||||
return existingMigrations, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var filename *string
|
||||
err := rows.Scan(&filename)
|
||||
if err != nil {
|
||||
return existingMigrations, err
|
||||
}
|
||||
existingMigrations = append(existingMigrations, *filename)
|
||||
}
|
||||
|
||||
return existingMigrations, nil
|
||||
}
|
||||
|
||||
// markMigrationSuccessful will add a row to the migration table
|
||||
func markMigrationSuccessful(db *sqlx.DB, filename string) error {
|
||||
// nolint:gosec
|
||||
query := fmt.Sprintf("INSERT INTO `%v` (filename) VALUES ($1)", mConfiguration.Table)
|
||||
_, err := db.Exec(query, filename)
|
||||
return err
|
||||
}
|
37
backend/internal/database/setup.go
Normal file
37
backend/internal/database/setup.go
Normal file
@ -0,0 +1,37 @@
|
||||
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)
|
||||
}
|
||||
}
|
74
backend/internal/database/sqlite.go
Normal file
74
backend/internal/database/sqlite.go
Normal file
@ -0,0 +1,74 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"npm/internal/config"
|
||||
"npm/internal/logger"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
// Blank import for Sqlite
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
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("sqlite3", 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()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user