From d874aed74b9e70bad90f0c59b4813a69fb323a8c Mon Sep 17 00:00:00 2001 From: codemage66 Date: Fri, 2 Sep 2016 02:53:38 -0400 Subject: [PATCH] Make it easier to add other storage systems https://github.com/blue-jay/blueprint/issues/45 https://github.com/blue-jay/blueprint/issues/46 Core database files moved to: https://github.com/blue-jay/core --- bootstrap/bootstrap.go | 14 +- .../20160630_020000.000000_init.down.sql | 11 - .../20160630_020000.000000_init.down.sql | 11 + .../20160630_020000.000000_init.up.sql | 18 +- env.json.example | 29 +-- generate/model/default.gen | 2 +- lib/database/database.go | 196 ------------------ lib/form/form.go | 4 +- model/note/note.go | 3 +- model/note/note_test.go | 105 ++++++++++ model/user/user.go | 3 +- 11 files changed, 157 insertions(+), 239 deletions(-) delete mode 100644 database/migration/20160630_020000.000000_init.down.sql create mode 100644 database/mysql/20160630_020000.000000_init.down.sql rename database/{migration => mysql}/20160630_020000.000000_init.up.sql (79%) delete mode 100644 lib/database/database.go create mode 100644 model/note/note_test.go diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index 54206b1..5cd1a02 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -11,7 +11,6 @@ import ( "github.com/blue-jay/blueprint/controller" "github.com/blue-jay/blueprint/controller/status" "github.com/blue-jay/blueprint/lib/asset" - "github.com/blue-jay/blueprint/lib/database" "github.com/blue-jay/blueprint/lib/email" "github.com/blue-jay/blueprint/lib/flash" "github.com/blue-jay/blueprint/lib/form" @@ -29,6 +28,7 @@ import ( "github.com/blue-jay/blueprint/viewmodify/authlevel" "github.com/blue-jay/blueprint/viewmodify/uri" + "github.com/blue-jay/core/storage/driver/mysql" "github.com/gorilla/context" "github.com/gorilla/csrf" ) @@ -40,12 +40,14 @@ import ( // Info contains the application settings. type Info struct { Asset asset.Info `json:"Asset"` - Database database.Info `json:"Database"` Email email.Info `json:"Email"` + Form form.Info `json:"Form"` + MySQL mysql.Info `json:"MySQL"` Server server.Info `json:"Server"` Session session.Info `json:"Session"` Template view.Template `json:"Template"` View view.Info `json:"View"` + Path string } // ParseJSON unmarshals bytes to structs @@ -74,6 +76,9 @@ func LoadConfig(configFile string) *Info { // Load the configuration file jsonconfig.LoadOrFatal(configFile, config) + // Store the path of the file + config.Path = configFile + // Return the configuration return config } @@ -90,10 +95,11 @@ func RegisterServices(config *Info) { }) // Connect to database - database.Connect(config.Database, true) + mysql.SetConfig(config.MySQL) + mysql.Connect(true) // Configure form handling - form.SetConfig(form.Info{config.Database.FileStorage}) + form.SetConfig(config.Form) // Load the controller routes controller.LoadRoutes() diff --git a/database/migration/20160630_020000.000000_init.down.sql b/database/migration/20160630_020000.000000_init.down.sql deleted file mode 100644 index bdb7f36..0000000 --- a/database/migration/20160630_020000.000000_init.down.sql +++ /dev/null @@ -1,11 +0,0 @@ -/* ***************************************************************************** -// Settings -// ****************************************************************************/ -SET foreign_key_checks = 0; - -/* ***************************************************************************** -// Remove tables -// ****************************************************************************/ -DROP TABLE IF EXISTS user; -DROP TABLE IF EXISTS user_status; -DROP TABLE IF EXISTS note; \ No newline at end of file diff --git a/database/mysql/20160630_020000.000000_init.down.sql b/database/mysql/20160630_020000.000000_init.down.sql new file mode 100644 index 0000000..7f7df83 --- /dev/null +++ b/database/mysql/20160630_020000.000000_init.down.sql @@ -0,0 +1,11 @@ +# ****************************************************************************** +# Settings +# ****************************************************************************** +SET foreign_key_checks = 0; + +# ****************************************************************************** +# Remove tables +# ****************************************************************************** +DROP TABLE IF EXISTS user; +DROP TABLE IF EXISTS user_status; +DROP TABLE IF EXISTS note; \ No newline at end of file diff --git a/database/migration/20160630_020000.000000_init.up.sql b/database/mysql/20160630_020000.000000_init.up.sql similarity index 79% rename from database/migration/20160630_020000.000000_init.up.sql rename to database/mysql/20160630_020000.000000_init.up.sql index 7e02909..4636e5a 100644 --- a/database/migration/20160630_020000.000000_init.up.sql +++ b/database/mysql/20160630_020000.000000_init.up.sql @@ -1,18 +1,18 @@ -/* ***************************************************************************** -// Settings -// ****************************************************************************/ +# ****************************************************************************** +# Settings +# ****************************************************************************** SET foreign_key_checks = 1; SET time_zone = '+00:00'; -/* ***************************************************************************** -// Create new tables -// ****************************************************************************/ +# ****************************************************************************** +# Create new tables +# ****************************************************************************** # CREATE DATABASE IF NOT EXISTS blueprint DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci; # USE blueprint; -/* ***************************************************************************** -// Create tables -// ****************************************************************************/ +# ****************************************************************************** +# Create tables +# ****************************************************************************** CREATE TABLE user_status ( id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, diff --git a/env.json.example b/env.json.example index c281437..45496a0 100644 --- a/env.json.example +++ b/env.json.example @@ -2,20 +2,6 @@ "Asset":{ "Folder":"asset" }, - "Database":{ - "FileStorage":"database/filestorage", - "Type":"MySQL", - "MySQL":{ - "Username":"root", - "Password":"", - "Database":"blueprint", - "Charset":"utf8mb4", - "Collation":"utf8mb4_unicode_ci", - "Hostname":"127.0.0.1", - "Port":3306, - "Parameter":"parseTime=true" - } - }, "Email":{ "Username":"", "Password":"", @@ -23,6 +9,21 @@ "Port":25, "From":"" }, + "Form":{ + "FileStorageFolder":"database/filestorage" + }, + "MySQL":{ + "Username":"root", + "Password":"", + "Database":"blueprint", + "Charset":"utf8mb4", + "Collation":"utf8mb4_unicode_ci", + "Hostname":"127.0.0.1", + "Port":3306, + "Parameter":"parseTime=true", + "MigrationFolder":"database/mysql", + "Extension":"sql" + }, "Server":{ "Hostname":"", "UseHTTP":true, diff --git a/generate/model/default.gen b/generate/model/default.gen index afbea8c..6ca1ed0 100644 --- a/generate/model/default.gen +++ b/generate/model/default.gen @@ -5,8 +5,8 @@ import ( "database/sql" "fmt" - "github.com/blue-jay/blueprint/lib/database" "github.com/blue-jay/blueprint/model" + database "github.com/blue-jay/storage/driver/mysql" "github.com/go-sql-driver/mysql" ) diff --git a/lib/database/database.go b/lib/database/database.go deleted file mode 100644 index 29937ea..0000000 --- a/lib/database/database.go +++ /dev/null @@ -1,196 +0,0 @@ -// Package database provides a wrapper around the sqlx package. -package database - -import ( - "errors" - "fmt" - "strings" - "sync" - - _ "github.com/go-sql-driver/mysql" // MySQL driver - "github.com/jmoiron/sqlx" -) - -// ***************************************************************************** -// Thread-Safe Configuration -// ***************************************************************************** - -var ( - info Info - infoMutex sync.RWMutex -) - -// Config returns the config. -func Config() Info { - infoMutex.RLock() - defer infoMutex.RUnlock() - return info -} - -// ResetConfig removes the config. -func ResetConfig() { - infoMutex.Lock() - info = Info{} - infoMutex.Unlock() -} - -// ***************************************************************************** -// Database Handling -// ***************************************************************************** - -// Type is the type of database. -type Type string - -const ( - // TypeMySQL is MySQL - TypeMySQL Type = "MySQL" -) - -// Info contains the database configurations. -type Info struct { - // FileStorage is the path to the folder where uploaded files are stored - FileStorage string - // Type of database - Type Type - // MySQL info if used - MySQL MySQLInfo -} - -// Connect to the database. -func Connect(i Info, connectDatabase bool) error { - var err error - - // Store the config - infoMutex.Lock() - info = i - infoMutex.Unlock() - - switch Config().Type { - case TypeMySQL: - // Connect to MySQL and ping - if SQL, err = sqlx.Connect("mysql", dsn(Config().MySQL, connectDatabase)); err != nil { - return err - } - default: - return errors.New("No registered database in config") - } - - return err -} - -// Disconnect the database connection. -func Disconnect() error { - switch Config().Type { - case TypeMySQL: - return SQL.Close() - default: - return errors.New("No registered database in config") - } -} - -// Create a new database. -func Create(ci MySQLInfo) error { - switch Config().Type { - case TypeMySQL: - // Set defaults - ci = setDefaults(ci) - - // Create the database - _, err := SQL.Exec(fmt.Sprintf(`CREATE DATABASE %v - DEFAULT CHARSET = %v - COLLATE = %v - ;`, ci.Database, - ci.Charset, - ci.Collation)) - return err - default: - return errors.New("No registered database in config") - } -} - -// Delete a database. -func Delete(ci MySQLInfo) error { - switch Config().Type { - case TypeMySQL: - // Delete the database - _, err := SQL.Exec(fmt.Sprintf(`DELETE DATABASE %v;`, ci.Database)) - return err - default: - return errors.New("No registered database in config") - } -} - -// ***************************************************************************** -// MySQL Specific -// ***************************************************************************** - -var ( - // SQL wrapper - SQL *sqlx.DB -) - -// MySQLInfo holds the details for the database connection. -type MySQLInfo struct { - Username string - Password string - Database string - Charset string - Collation string - Hostname string - Port int - Parameter string -} - -// DSN returns the Data Source Name. -func dsn(ci MySQLInfo, includeDatabase bool) string { - // Set defaults - ci = setDefaults(ci) - - // Build parameters - param := ci.Parameter - - // If parameter is specified, add a question mark - // Don't add one if a question mark is already there - if len(ci.Parameter) > 0 && !strings.HasPrefix(ci.Parameter, "?") { - param = "?" + ci.Parameter - } - - // Add collation - if !strings.Contains(param, "collation") { - if len(param) > 0 { - param += "&collation=" + ci.Collation - } else { - param = "?collation=" + ci.Collation - } - } - - // Add charset - if !strings.Contains(param, "charset") { - if len(param) > 0 { - param += "&charset=" + ci.Charset - } else { - param = "?charset=" + ci.Charset - } - } - - // Example: root:password@tcp(localhost:3306)/test - s := fmt.Sprintf("%v:%v@tcp(%v:%d)/%v", ci.Username, ci.Password, ci.Hostname, ci.Port, param) - - if includeDatabase { - s = fmt.Sprintf("%v:%v@tcp(%v:%d)/%v%v", ci.Username, ci.Password, ci.Hostname, ci.Port, ci.Database, param) - } - - return s -} - -// setDefaults sets the charset and collation if they are not set. -func setDefaults(ci MySQLInfo) MySQLInfo { - if len(ci.Charset) == 0 { - ci.Charset = "utf8" - } - if len(ci.Collation) == 0 { - ci.Collation = "utf8_unicode_ci" - } - - return ci -} diff --git a/lib/form/form.go b/lib/form/form.go index b494ef2..752185a 100644 --- a/lib/form/form.go +++ b/lib/form/form.go @@ -29,7 +29,7 @@ var ( // Info holds the details for the form handling. type Info struct { - FileStorage string + FileStorageFolder string `json:"FileStorageFolder"` } // SetConfig stores the config. @@ -90,7 +90,7 @@ func UploadFile(r *http.Request, name string, maxSize int64) (string, string, er return "", "", err } - f, err := os.OpenFile(filepath.Join(Config().FileStorage, fileID), os.O_WRONLY|os.O_CREATE, 0644) + f, err := os.OpenFile(filepath.Join(Config().FileStorageFolder, fileID), os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return "", "", err } diff --git a/model/note/note.go b/model/note/note.go index a4dae04..528929c 100644 --- a/model/note/note.go +++ b/model/note/note.go @@ -5,8 +5,9 @@ import ( "database/sql" "fmt" - "github.com/blue-jay/blueprint/lib/database" "github.com/blue-jay/blueprint/model" + + database "github.com/blue-jay/core/storage/driver/mysql" "github.com/go-sql-driver/mysql" ) diff --git a/model/note/note_test.go b/model/note/note_test.go new file mode 100644 index 0000000..a59f1b7 --- /dev/null +++ b/model/note/note_test.go @@ -0,0 +1,105 @@ +package note_test + +import ( + "fmt" + "os" + "testing" + + "github.com/blue-jay/blueprint/model/note" + "github.com/blue-jay/blueprint/model/user" + + "github.com/blue-jay/core/storage/migration" + "github.com/blue-jay/core/storage/migration/mysql" +) + +var ( + mig *migration.Info +) + +// TestMain runs setup, tests, and then teardown. +func TestMain(m *testing.M) { + setup() + returnCode := m.Run() + teardown() + os.Exit(returnCode) +} + +// setup handles any start up tasks. +func setup() { + mysql.SetUp("../../env.json", "database_test") +} + +// teardown handles any clean up tasks. +func teardown() { + mysql.TearDown() +} + +// TestComplete +func TestComplete(t *testing.T) { + data := "Test data." + dataNew := "New test data." + + result, err := user.Create("John", "Doe", "jdoe@domain.com", "p@$$W0rD") + if err != nil { + t.Error("could not create user:", err) + } + + uID, err := result.LastInsertId() + if err != nil { + t.Error("could not convert user ID:", err) + } + + // Convert ID to string + userID := fmt.Sprintf("%v", uID) + + // Create a record + result, err = note.Create(data, userID) + if err != nil { + t.Error("could not create record:", err) + } + + // Get the last ID + ID, err := result.LastInsertId() + if err != nil { + t.Error("could not convert ID:", err) + } + + // Convert ID to string + lastID := fmt.Sprintf("%v", ID) + + // Select a record + record, err := note.ByID(lastID, userID) + if err != nil { + t.Error("could not retrieve record:", err) + } else if record.Name != data { + t.Errorf("retrieved wrong record: got '%v' want '%v'", record.Name, data) + } + + // Update a record + result, err = note.Update(dataNew, lastID, userID) + if err != nil { + t.Error("could not update record:", err) + } + + // Select a record + record, err = note.ByID(lastID, userID) + if err != nil { + t.Error("could not retrieve record:", err) + } else if record.Name != dataNew { + t.Errorf("retieved wrong record: got '%v' want '%v'", record.Name, dataNew) + } + + // Delete a record by ID + result, err = note.Delete(lastID, userID) + if err != nil { + t.Error("could not delete record:", err) + } + + // Count the number of deleted rows + rows, err := result.RowsAffected() + if err != nil { + t.Error("could not count affected rows:", err) + } else if rows != 1 { + t.Error("incorrect number of affected rows:", rows) + } +} diff --git a/model/user/user.go b/model/user/user.go index 3886db7..b21c135 100644 --- a/model/user/user.go +++ b/model/user/user.go @@ -5,8 +5,9 @@ import ( "database/sql" "fmt" - "github.com/blue-jay/blueprint/lib/database" "github.com/blue-jay/blueprint/model" + + database "github.com/blue-jay/core/storage/driver/mysql" "github.com/go-sql-driver/mysql" )