mirror of
https://github.com/documize/community.git
synced 2025-07-19 13:19:43 +02:00
Run multiple sql files to update the db as required
Locks the config table to make sure only one instance does the update. Refactors the start-up against an empty database code to also use the same update mechanism.
This commit is contained in:
parent
a3cfb06ef7
commit
8fcf67ef17
35 changed files with 251 additions and 178 deletions
|
@ -17,8 +17,6 @@ export default Ember.Route.extend({
|
||||||
if (pwd.length === 0 || pwd === "{{.DBhash}}") {
|
if (pwd.length === 0 || pwd === "{{.DBhash}}") {
|
||||||
this.transitionTo('auth.login'); // don't allow access to this page if we are not in setup mode, kick them out altogether
|
this.transitionTo('auth.login'); // don't allow access to this page if we are not in setup mode, kick them out altogether
|
||||||
}
|
}
|
||||||
|
|
||||||
this.session.clearSession();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
|
|
|
@ -20,6 +20,7 @@ const {
|
||||||
|
|
||||||
export default Ember.Service.extend({
|
export default Ember.Service.extend({
|
||||||
ajax: service(),
|
ajax: service(),
|
||||||
|
localStorage: service(),
|
||||||
|
|
||||||
endpoint: `${config.apiHost}/${config.apiNamespace}`,
|
endpoint: `${config.apiHost}/${config.apiNamespace}`,
|
||||||
orgId: '',
|
orgId: '',
|
||||||
|
@ -27,6 +28,7 @@ export default Ember.Service.extend({
|
||||||
version: '',
|
version: '',
|
||||||
message: '',
|
message: '',
|
||||||
allowAnonymousAccess: false,
|
allowAnonymousAccess: false,
|
||||||
|
setupMode: false,
|
||||||
|
|
||||||
getBaseUrl(endpoint) {
|
getBaseUrl(endpoint) {
|
||||||
return [this.get('host'), endpoint].join('/');
|
return [this.get('host'), endpoint].join('/');
|
||||||
|
@ -40,12 +42,14 @@ export default Ember.Service.extend({
|
||||||
|
|
||||||
let isInSetupMode = dbhash && dbhash !== "{{.DBhash}}";
|
let isInSetupMode = dbhash && dbhash !== "{{.DBhash}}";
|
||||||
if (isInSetupMode) {
|
if (isInSetupMode) {
|
||||||
this.setProperites({
|
this.setProperties({
|
||||||
title: htmlSafe("Documize Setup"),
|
title: htmlSafe("Documize Setup"),
|
||||||
allowAnonymousAccess: false
|
allowAnonymousAccess: true,
|
||||||
|
setupMode: true
|
||||||
});
|
});
|
||||||
|
this.get('localStorage').clearAll();
|
||||||
|
|
||||||
return resolve();
|
return resolve(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.get('ajax').request('public/meta').then((response) => {
|
return this.get('ajax').request('public/meta').then((response) => {
|
||||||
|
|
|
@ -22,5 +22,9 @@ export default Ember.Service.extend({
|
||||||
|
|
||||||
clearSessionItem: function (key) {
|
clearSessionItem: function (key) {
|
||||||
delete localStorage[key];
|
delete localStorage[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
clearAll() {
|
||||||
|
localStorage.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
2
build.sh
2
build.sh
|
@ -37,7 +37,7 @@ go generate
|
||||||
|
|
||||||
echo "Compiling app..."
|
echo "Compiling app..."
|
||||||
cd ../..
|
cd ../..
|
||||||
for arch in amd64 386 ; do
|
for arch in amd64 ; do
|
||||||
for os in darwin linux windows ; do
|
for os in darwin linux windows ; do
|
||||||
if [ "$os" == "windows" ] ; then
|
if [ "$os" == "windows" ] ; then
|
||||||
echo "Compiling documize-$os-$arch.exe"
|
echo "Compiling documize-$os-$arch.exe"
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/documize/community/documize/api/request"
|
"github.com/documize/community/documize/api/request"
|
||||||
"github.com/documize/community/documize/api/util"
|
"github.com/documize/community/documize/api/util"
|
||||||
"github.com/documize/community/documize/section/provider"
|
"github.com/documize/community/documize/section/provider"
|
||||||
|
"github.com/documize/community/documize/web"
|
||||||
"github.com/documize/community/wordsmith/environment"
|
"github.com/documize/community/wordsmith/environment"
|
||||||
"github.com/documize/community/wordsmith/log"
|
"github.com/documize/community/wordsmith/log"
|
||||||
"github.com/documize/community/wordsmith/utility"
|
"github.com/documize/community/wordsmith/utility"
|
||||||
|
@ -293,7 +294,8 @@ func preAuthorizeStaticAssets(r *http.Request) bool {
|
||||||
strings.ToLower(r.URL.Path) == "/favicon.ico" ||
|
strings.ToLower(r.URL.Path) == "/favicon.ico" ||
|
||||||
strings.ToLower(r.URL.Path) == "/robots.txt" ||
|
strings.ToLower(r.URL.Path) == "/robots.txt" ||
|
||||||
strings.ToLower(r.URL.Path) == "/version" ||
|
strings.ToLower(r.URL.Path) == "/version" ||
|
||||||
strings.HasPrefix(strings.ToLower(r.URL.Path), "/api/public/") {
|
strings.HasPrefix(strings.ToLower(r.URL.Path), "/api/public/") ||
|
||||||
|
((web.SiteMode == web.SiteModeSetup) && (strings.ToLower(r.URL.Path) == "/api/setup")) {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,10 @@ func buildUnsecureRoutes() *mux.Router {
|
||||||
func buildSecureRoutes() *mux.Router {
|
func buildSecureRoutes() *mux.Router {
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
|
if web.SiteMode == web.SiteModeSetup {
|
||||||
|
router.HandleFunc("/api/setup", database.Create).Methods("POST", "OPTIONS")
|
||||||
|
}
|
||||||
|
|
||||||
// Import & Convert Document
|
// Import & Convert Document
|
||||||
router.HandleFunc("/api/import/folder/{folderID}", UploadConvertDocument).Methods("POST", "OPTIONS")
|
router.HandleFunc("/api/import/folder/{folderID}", UploadConvertDocument).Methods("POST", "OPTIONS")
|
||||||
|
|
||||||
|
@ -254,7 +258,6 @@ func AppRouter() *mux.Router {
|
||||||
log.Info("Serving OFFLINE web app")
|
log.Info("Serving OFFLINE web app")
|
||||||
case web.SiteModeSetup:
|
case web.SiteModeSetup:
|
||||||
log.Info("Serving SETUP web app")
|
log.Info("Serving SETUP web app")
|
||||||
router.HandleFunc("/setup", database.Create).Methods("POST", "OPTIONS")
|
|
||||||
case web.SiteModeBadDB:
|
case web.SiteModeBadDB:
|
||||||
log.Info("Serving BAD DATABASE web app")
|
log.Info("Serving BAD DATABASE web app")
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
package request
|
package request
|
||||||
|
|
||||||
/* TODO(Elliott)
|
/* TODO(Elliott)
|
||||||
import (
|
import (
|
||||||
"github.com/documize/community/documize/api/entity"
|
"github.com/documize/community/documize/api/entity"
|
||||||
|
|
|
@ -67,6 +67,29 @@ func ConfigString(area, path string) (ret string) {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigSet writes a configuration JSON element to the config table.
|
||||||
|
func ConfigSet(area, json string) error {
|
||||||
|
if Db == nil {
|
||||||
|
return errors.New("no database")
|
||||||
|
}
|
||||||
|
if area == "" {
|
||||||
|
return errors.New("no area")
|
||||||
|
}
|
||||||
|
sql := "INSERT INTO `config` (`key`,`config`) " +
|
||||||
|
"VALUES ('" + area + "','" + json +
|
||||||
|
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
|
||||||
|
|
||||||
|
stmt, err := Db.Preparex(sql)
|
||||||
|
if err != nil {
|
||||||
|
//fmt.Printf("DEBUG: Unable to prepare select SQL for ConfigSet: %s -- error: %v\n", sql, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer utility.Close(stmt)
|
||||||
|
|
||||||
|
_, err = stmt.Exec()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// UserConfigGetJSON fetches a configuration JSON element from the userconfig table for a given orgid/userid combination.
|
// UserConfigGetJSON fetches a configuration JSON element from the userconfig table for a given orgid/userid combination.
|
||||||
// Errors return the empty string. A blank path returns the whole JSON object, as JSON.
|
// Errors return the empty string. A blank path returns the whole JSON object, as JSON.
|
||||||
func UserConfigGetJSON(orgid, userid, area, path string) (ret string) {
|
func UserConfigGetJSON(orgid, userid, area, path string) (ret string) {
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
package request
|
package request
|
||||||
|
|
||||||
/* TODO(Elliott)
|
/* TODO(Elliott)
|
||||||
import (
|
import (
|
||||||
"github.com/documize/community/wordsmith/environment"
|
"github.com/documize/community/wordsmith/environment"
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
package request
|
package request
|
||||||
|
|
||||||
/* TODO(Elliott)
|
/* TODO(Elliott)
|
||||||
import (
|
import (
|
||||||
"github.com/documize/community/documize/api/entity"
|
"github.com/documize/community/documize/api/entity"
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
package request
|
package request
|
||||||
|
|
||||||
/* TODO(Elliott)
|
/* TODO(Elliott)
|
||||||
import "testing"
|
import "testing"
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
package request
|
package request
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -69,18 +68,8 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// go into setup mode if required
|
// go into setup mode if required
|
||||||
if database.Check(Db, connectionString,
|
if database.Check(Db, connectionString) {
|
||||||
func() (bool, error) {
|
if err := database.Migrate(true /* the config table exists */); err != nil {
|
||||||
// LockDB locks the database for migrations, returning if locked and an error.
|
|
||||||
// TODO, and if lock fails, wait here until it unlocks
|
|
||||||
return false, errors.New("LockDB TODO")
|
|
||||||
},
|
|
||||||
func() {
|
|
||||||
// UnlockDB unlocks the database for migrations.
|
|
||||||
// Reports errors in the log.
|
|
||||||
// TODO
|
|
||||||
}) {
|
|
||||||
if err := database.Migrate(ConfigString("META", "database")); err != nil {
|
|
||||||
log.Error("Unable to run database migration: ", err)
|
log.Error("Unable to run database migration: ", err)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
package request
|
package request
|
||||||
|
|
||||||
/* TODO(Elliott)
|
/* TODO(Elliott)
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
package request
|
package request
|
||||||
|
|
||||||
/* TODO(Elliott)
|
/* TODO(Elliott)
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
package request
|
package request
|
||||||
|
|
||||||
/* TODO(Elliott)
|
/* TODO(Elliott)
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
package request
|
package request
|
||||||
|
|
||||||
/* TODO(Elliott)
|
/* TODO(Elliott)
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
|
|
||||||
"github.com/documize/community/documize/web"
|
"github.com/documize/community/documize/web"
|
||||||
"github.com/documize/community/wordsmith/log"
|
"github.com/documize/community/wordsmith/log"
|
||||||
|
"github.com/documize/community/wordsmith/utility"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,18 +28,12 @@ var dbCheckOK bool // default false
|
||||||
// dbPtr is a pointer to the central connection to the database, used by all database requests.
|
// dbPtr is a pointer to the central connection to the database, used by all database requests.
|
||||||
var dbPtr **sqlx.DB
|
var dbPtr **sqlx.DB
|
||||||
|
|
||||||
// lockDB locks the database
|
|
||||||
var lockDB func() (bool, error)
|
|
||||||
|
|
||||||
// unlockDB unlocks the database
|
|
||||||
var unlockDB func()
|
|
||||||
|
|
||||||
// Check that the database is configured correctly and that all the required tables exist.
|
// Check that the database is configured correctly and that all the required tables exist.
|
||||||
// It must be the first function called in the
|
// It must be the first function called in this package.
|
||||||
func Check(Db *sqlx.DB, connectionString string, lDB func() (bool, error), ulDB func()) bool {
|
func Check(Db *sqlx.DB, connectionString string) bool {
|
||||||
dbPtr = &Db
|
dbPtr = &Db
|
||||||
lockDB = lDB
|
|
||||||
unlockDB = ulDB
|
log.Info("Running database checks, this may take a while...")
|
||||||
|
|
||||||
csBits := strings.Split(connectionString, "/")
|
csBits := strings.Split(connectionString, "/")
|
||||||
if len(csBits) > 1 {
|
if len(csBits) > 1 {
|
||||||
|
@ -52,7 +47,7 @@ func Check(Db *sqlx.DB, connectionString string, lDB func() (bool, error), ulDB
|
||||||
web.SiteMode = web.SiteModeBadDB
|
web.SiteMode = web.SiteModeBadDB
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer rows.Close() // ignore error
|
defer utility.Close(rows)
|
||||||
var version, charset, collation string
|
var version, charset, collation string
|
||||||
if rows.Next() {
|
if rows.Next() {
|
||||||
err = rows.Scan(&version, &charset, &collation)
|
err = rows.Scan(&version, &charset, &collation)
|
||||||
|
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -25,6 +24,7 @@ import (
|
||||||
"github.com/documize/community/wordsmith/utility"
|
"github.com/documize/community/wordsmith/utility"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// runSQL creates a transaction per call
|
||||||
func runSQL(sql string) (id uint64, err error) {
|
func runSQL(sql string) (id uint64, err error) {
|
||||||
|
|
||||||
if strings.TrimSpace(sql) == "" {
|
if strings.TrimSpace(sql) == "" {
|
||||||
|
@ -41,7 +41,7 @@ func runSQL(sql string) (id uint64, err error) {
|
||||||
result, err := tx.Exec(sql)
|
result, err := tx.Exec(sql)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback() // ignore error as already in an error state
|
log.IfErr(tx.Rollback())
|
||||||
log.Error("runSql - unable to run sql", err)
|
log.Error("runSql - unable to run sql", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -59,14 +59,6 @@ func runSQL(sql string) (id uint64, err error) {
|
||||||
|
|
||||||
// Create the tables in a blank database
|
// Create the tables in a blank database
|
||||||
func Create(w http.ResponseWriter, r *http.Request) {
|
func Create(w http.ResponseWriter, r *http.Request) {
|
||||||
txt := "database.Create()"
|
|
||||||
//defer func(){fmt.Println("DEBUG"+txt)}()
|
|
||||||
|
|
||||||
if dbCheckOK {
|
|
||||||
txt += " Check OK"
|
|
||||||
} else {
|
|
||||||
txt += " Check not OK"
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
target := "/setup"
|
target := "/setup"
|
||||||
|
@ -92,14 +84,9 @@ func Create(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
txt += fmt.Sprintf("\n%#v\n", r.Form)
|
|
||||||
|
|
||||||
dbname := r.Form.Get("dbname")
|
dbname := r.Form.Get("dbname")
|
||||||
dbhash := r.Form.Get("dbhash")
|
dbhash := r.Form.Get("dbhash")
|
||||||
|
|
||||||
txt += fmt.Sprintf("DBname:%s (want:%s) DBhash: %s (want:%s)\n",
|
|
||||||
dbname, web.SiteInfo.DBname, dbhash, web.SiteInfo.DBhash)
|
|
||||||
|
|
||||||
if dbname != web.SiteInfo.DBname || dbhash != web.SiteInfo.DBhash {
|
if dbname != web.SiteInfo.DBname || dbhash != web.SiteInfo.DBhash {
|
||||||
log.Error("database.Create()'s security credentials error ", errors.New("bad db name or validation code"))
|
log.Error("database.Create()'s security credentials error ", errors.New("bad db name or validation code"))
|
||||||
return
|
return
|
||||||
|
@ -117,8 +104,6 @@ func Create(w http.ResponseWriter, r *http.Request) {
|
||||||
Revised: time.Now(),
|
Revised: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
txt += fmt.Sprintf("\n%#v\n", details)
|
|
||||||
|
|
||||||
if details.Company == "" ||
|
if details.Company == "" ||
|
||||||
details.CompanyLong == "" ||
|
details.CompanyLong == "" ||
|
||||||
details.Message == "" ||
|
details.Message == "" ||
|
||||||
|
@ -126,43 +111,12 @@ func Create(w http.ResponseWriter, r *http.Request) {
|
||||||
details.Password == "" ||
|
details.Password == "" ||
|
||||||
details.Firstname == "" ||
|
details.Firstname == "" ||
|
||||||
details.Lastname == "" {
|
details.Lastname == "" {
|
||||||
txt += "ERROR: required field blank"
|
log.Error("database.Create() error ",
|
||||||
|
errors.New("required field in database set-up form blank"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
firstSQL := "db_00000.sql"
|
if err = Migrate(false /* no tables exist yet */); err != nil {
|
||||||
|
|
||||||
buf, err := web.ReadFile("scripts/" + firstSQL)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("database.Create()'s web.ReadFile()", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx, err := (*dbPtr).Beginx()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(" failed to get transaction", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
stmts := getStatements(buf)
|
|
||||||
|
|
||||||
for i, stmt := range stmts {
|
|
||||||
_, err = tx.Exec(stmt)
|
|
||||||
txt += fmt.Sprintf("%d: %s\nResult: %v\n\n", i, stmt, err)
|
|
||||||
if err != nil {
|
|
||||||
tx.Rollback() // ignore error as already in an error state
|
|
||||||
log.Error("database.Create() unable to run table create sql", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("database.Create()", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Migrate(firstSQL); err != nil {
|
|
||||||
log.Error("database.Create()", err)
|
log.Error("database.Create()", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -174,7 +128,6 @@ func Create(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
web.SiteMode = web.SiteModeNormal
|
web.SiteMode = web.SiteModeNormal
|
||||||
txt += "\n Success!\n"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The result of completing the onboarding process.
|
// The result of completing the onboarding process.
|
||||||
|
@ -219,7 +172,6 @@ func setupAccount(completion onboardRequest, serial string) (err error) {
|
||||||
log.Error("Failed with error", err)
|
log.Error("Failed with error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
// Link user to organization.
|
// Link user to organization.
|
||||||
accountID := util.UniqueID()
|
accountID := util.UniqueID()
|
||||||
|
@ -250,19 +202,3 @@ func setupAccount(completion onboardRequest, serial string) (err error) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// getStatement strips out the comments and returns all the individual SQL commands (apart from "USE") as a []string.
|
|
||||||
func getStatements(bytes []byte) []string {
|
|
||||||
/* Strip comments of the form '-- comment' or like this one */
|
|
||||||
stripped := regexp.MustCompile("(?s)--.*?\n|/\\*.*?\\*/").ReplaceAll(bytes, []byte("\n"))
|
|
||||||
sqls := strings.Split(string(stripped), ";")
|
|
||||||
ret := make([]string, 0, len(sqls))
|
|
||||||
for _, v := range sqls {
|
|
||||||
trimmed := strings.TrimSpace(v)
|
|
||||||
if len(trimmed) > 0 &&
|
|
||||||
!strings.HasPrefix(strings.ToUpper(trimmed), "USE ") { // make sure we don't USE the wrong database
|
|
||||||
ret = append(ret, trimmed+";")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,11 +12,17 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
"github.com/documize/community/documize/web"
|
"github.com/documize/community/documize/web"
|
||||||
|
"github.com/documize/community/wordsmith/log"
|
||||||
|
"github.com/documize/community/wordsmith/utility"
|
||||||
)
|
)
|
||||||
|
|
||||||
const migrationsDir = "bindata/scripts"
|
const migrationsDir = "bindata/scripts"
|
||||||
|
@ -41,6 +47,10 @@ func migrations(lastMigration string) (migrationsT, error) {
|
||||||
|
|
||||||
hadLast := false
|
hadLast := false
|
||||||
|
|
||||||
|
if len(lastMigration) == 0 {
|
||||||
|
hadLast = true
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range files {
|
for _, v := range files {
|
||||||
if v == lastMigration {
|
if v == lastMigration {
|
||||||
hadLast = true
|
hadLast = true
|
||||||
|
@ -56,35 +66,130 @@ func migrations(lastMigration string) (migrationsT, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// migrate the database as required, by applying the migrations.
|
// migrate the database as required, by applying the migrations.
|
||||||
func (m migrationsT) migrate() error {
|
func (m migrationsT) migrate(tx *sqlx.Tx) error {
|
||||||
for _, v := range m {
|
for _, v := range m {
|
||||||
|
log.Info("Processing migration file: " + v)
|
||||||
buf, err := web.Asset(migrationsDir + "/" + v)
|
buf, err := web.Asset(migrationsDir + "/" + v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("DEBUG database.Migrate() ", v, ":\n", string(buf)) // TODO actually run the SQL
|
//fmt.Println("DEBUG database.Migrate() ", v, ":\n", string(buf)) // TODO actually run the SQL
|
||||||
|
err = processSQLfile(tx, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
json := `{"database":"` + v + `"}`
|
||||||
|
sql := "INSERT INTO `config` (`key`,`config`) " +
|
||||||
|
"VALUES ('META','" + json +
|
||||||
|
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
|
||||||
|
|
||||||
|
_, err = tx.Exec(sql)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Println("DEBUG insert 10s wait for testing")
|
||||||
|
//time.Sleep(10 * time.Second)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate the database as required, consolidated action.
|
func migrateEnd(tx *sqlx.Tx, err error) error {
|
||||||
func Migrate(lastMigration string) error {
|
if tx != nil {
|
||||||
mig, err := migrations(lastMigration)
|
_, ulerr := tx.Exec("UNLOCK TABLES;")
|
||||||
if err != nil {
|
log.IfErr(ulerr)
|
||||||
return err
|
if err == nil {
|
||||||
}
|
log.IfErr(tx.Commit())
|
||||||
if len(mig) == 0 {
|
log.Info("Database migration completed.")
|
||||||
return nil // no migrations to perform
|
|
||||||
}
|
|
||||||
locked, err := lockDB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if locked {
|
|
||||||
defer unlockDB()
|
|
||||||
if err := mig.migrate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
log.IfErr(tx.Rollback())
|
||||||
|
}
|
||||||
|
log.Error("Database migration failed: ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate the database as required, consolidated action.
|
||||||
|
func Migrate(ConfigTableExists bool) error {
|
||||||
|
|
||||||
|
lastMigration := ""
|
||||||
|
|
||||||
|
tx, err := (*dbPtr).Beginx()
|
||||||
|
if err != nil {
|
||||||
|
return migrateEnd(tx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ConfigTableExists {
|
||||||
|
_, err = tx.Exec("LOCK TABLE `config` WRITE;")
|
||||||
|
if err != nil {
|
||||||
|
return migrateEnd(tx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Database migration lock taken.")
|
||||||
|
|
||||||
|
var stmt *sql.Stmt
|
||||||
|
stmt, err = tx.Prepare("SELECT JSON_EXTRACT(`config`,'$.database') FROM `config` WHERE `key` = 'META';")
|
||||||
|
if err == nil {
|
||||||
|
defer utility.Close(stmt)
|
||||||
|
var item = make([]uint8, 0)
|
||||||
|
|
||||||
|
row := stmt.QueryRow()
|
||||||
|
|
||||||
|
err = row.Scan(&item)
|
||||||
|
if err != nil {
|
||||||
|
return migrateEnd(tx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(item) > 1 {
|
||||||
|
q := []byte(`"`)
|
||||||
|
lastMigration = string(bytes.TrimPrefix(bytes.TrimSuffix(item, q), q))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info("Database migration last previously applied file was: " + lastMigration)
|
||||||
|
}
|
||||||
|
|
||||||
|
mig, err := migrations(lastMigration)
|
||||||
|
if err != nil {
|
||||||
|
return migrateEnd(tx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mig) == 0 {
|
||||||
|
log.Info("Database migration no updates to perform.")
|
||||||
|
return migrateEnd(tx, nil) // no migrations to perform
|
||||||
|
}
|
||||||
|
log.Info("Database migration will execute the following update files: " + strings.Join([]string(mig), ", "))
|
||||||
|
|
||||||
|
return migrateEnd(tx, mig.migrate(tx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func processSQLfile(tx *sqlx.Tx, buf []byte) error {
|
||||||
|
|
||||||
|
stmts := getStatements(buf)
|
||||||
|
|
||||||
|
for _, stmt := range stmts {
|
||||||
|
|
||||||
|
_, err := tx.Exec(stmt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStatement strips out the comments and returns all the individual SQL commands (apart from "USE") as a []string.
|
||||||
|
func getStatements(bytes []byte) []string {
|
||||||
|
/* Strip comments of the form '-- comment' or like this one */
|
||||||
|
stripped := regexp.MustCompile("(?s)--.*?\n|/\\*.*?\\*/").ReplaceAll(bytes, []byte("\n"))
|
||||||
|
sqls := strings.Split(string(stripped), ";")
|
||||||
|
ret := make([]string, 0, len(sqls))
|
||||||
|
for _, v := range sqls {
|
||||||
|
trimmed := strings.TrimSpace(v)
|
||||||
|
if len(trimmed) > 0 &&
|
||||||
|
!strings.HasPrefix(strings.ToUpper(trimmed), "USE ") { // make sure we don't USE the wrong database
|
||||||
|
ret = append(ret, trimmed+";")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CallbackT is the type signature of the callback function of GetString().
|
// CallbackT is the type signature of the callback function of GetString().
|
||||||
|
@ -36,6 +37,7 @@ type varsT struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var vars varsT
|
var vars varsT
|
||||||
|
var varsMutex sync.Mutex
|
||||||
|
|
||||||
// Len is part of sort.Interface.
|
// Len is part of sort.Interface.
|
||||||
func (v *varsT) Len() int {
|
func (v *varsT) Len() int {
|
||||||
|
@ -59,6 +61,8 @@ const goInit = "(default)"
|
||||||
|
|
||||||
// GetString sets-up the flag for later use, it must be called before ParseOK(), usually in an init().
|
// GetString sets-up the flag for later use, it must be called before ParseOK(), usually in an init().
|
||||||
func GetString(target *string, name string, required bool, usage string, callback CallbackT) {
|
func GetString(target *string, name string, required bool, usage string, callback CallbackT) {
|
||||||
|
varsMutex.Lock()
|
||||||
|
defer varsMutex.Unlock()
|
||||||
name = strings.ToLower(strings.TrimSpace(name))
|
name = strings.ToLower(strings.TrimSpace(name))
|
||||||
setter := Prefix + strings.ToUpper(name)
|
setter := Prefix + strings.ToUpper(name)
|
||||||
value := os.Getenv(setter)
|
value := os.Getenv(setter)
|
||||||
|
@ -76,6 +80,8 @@ var showSettings = flag.Bool("showsettings", false, "if true, show settings in t
|
||||||
// It should be the first thing called by any main() that uses this library.
|
// It should be the first thing called by any main() that uses this library.
|
||||||
// If all the required variables are not present, it prints an error and calls os.Exit(2) like flag.Parse().
|
// If all the required variables are not present, it prints an error and calls os.Exit(2) like flag.Parse().
|
||||||
func Parse(doFirst string) {
|
func Parse(doFirst string) {
|
||||||
|
varsMutex.Lock()
|
||||||
|
defer varsMutex.Unlock()
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
sort.Sort(&vars)
|
sort.Sort(&vars)
|
||||||
for pass := 1; pass <= 2; pass++ {
|
for pass := 1; pass <= 2; pass++ {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue