Migration implementation, auto migrate when starting program
This commit is contained in:
		
							
								
								
									
										105
									
								
								database/migrate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								database/migrate.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | |||||||
|  | package database | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"GoWeb/app" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/lib/pq" | ||||||
|  | 	"log" | ||||||
|  | 	"reflect" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Migrate given a dummy object of any type, it will create a table with the same name as the type and create columns with the same name as the fields of the object | ||||||
|  | func Migrate(app *app.App, anyStruct interface{}) error { | ||||||
|  | 	valueOfStruct := reflect.ValueOf(anyStruct) | ||||||
|  | 	typeOfStruct := valueOfStruct.Type() | ||||||
|  |  | ||||||
|  | 	tableName := typeOfStruct.Name() | ||||||
|  | 	err := createTable(app, tableName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i := 0; i < valueOfStruct.NumField(); i++ { | ||||||
|  | 		fieldType := typeOfStruct.Field(i) | ||||||
|  | 		fieldName := fieldType.Name | ||||||
|  | 		if fieldName != "Id" && fieldName != "id" { | ||||||
|  | 			err := createColumn(app, tableName, fieldName, fieldType.Type.Name()) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // createTable creates a table with the given name if it doesn't exist, it is assumed that id will be the primary key | ||||||
|  | func createTable(app *app.App, tableName string) error { | ||||||
|  | 	sanitizedTableQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS \"%s\" (\"Id\" serial primary key)", tableName) | ||||||
|  |  | ||||||
|  | 	_, err := app.Db.Query(sanitizedTableQuery) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println("Error creating table: " + tableName) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	log.Println("Table created successfully (or already exists): " + tableName) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // createColumn creates a column with the given name and type if it doesn't exist | ||||||
|  | func createColumn(app *app.App, tableName, columnName, columnType string) error { | ||||||
|  | 	postgresType, err := getPostgresType(columnType) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println("Error creating column: " + columnName + " in table: " + tableName + " with type: " + postgresType) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sanitizedTableName := pq.QuoteIdentifier(tableName) | ||||||
|  | 	query := fmt.Sprintf("ALTER TABLE %s ADD COLUMN IF NOT EXISTS \"%s\" %s", sanitizedTableName, columnName, postgresType) | ||||||
|  |  | ||||||
|  | 	_, err = app.Db.Query(query) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println("Error creating column: " + columnName + " in table: " + tableName + " with type: " + postgresType) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	log.Println("Column created successfully (or already exists):", columnName) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Given a type in Go, return the corresponding type in Postgres | ||||||
|  | func getPostgresType(goType string) (string, error) { | ||||||
|  | 	switch goType { | ||||||
|  | 	case "int": | ||||||
|  | 	case "int32": | ||||||
|  | 	case "uint": | ||||||
|  | 	case "uint32": | ||||||
|  | 		return "integer", nil | ||||||
|  | 	case "int64": | ||||||
|  | 	case "uint64": | ||||||
|  | 		return "bigint", nil | ||||||
|  | 	case "int16": | ||||||
|  | 	case "int8": | ||||||
|  | 	case "uint16": | ||||||
|  | 	case "uint8": | ||||||
|  | 	case "byte": | ||||||
|  | 		return "smallint", nil | ||||||
|  | 	case "string": | ||||||
|  | 		return "text", nil | ||||||
|  | 	case "float64": | ||||||
|  | 		return "double precision", nil | ||||||
|  | 	case "bool": | ||||||
|  | 		return "boolean", nil | ||||||
|  | 	case "time.Time": | ||||||
|  | 		return "timestamp", nil | ||||||
|  | 	case "[]byte": | ||||||
|  | 		return "bytea", nil | ||||||
|  | 	default: | ||||||
|  | 		return "text", nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return "", errors.New("Unknown type: " + goType) | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								main.go
									
									
									
									
									
								
							| @@ -4,6 +4,7 @@ import ( | |||||||
| 	"GoWeb/app" | 	"GoWeb/app" | ||||||
| 	"GoWeb/config" | 	"GoWeb/config" | ||||||
| 	"GoWeb/database" | 	"GoWeb/database" | ||||||
|  | 	"GoWeb/models" | ||||||
| 	"GoWeb/routes" | 	"GoWeb/routes" | ||||||
| 	"embed" | 	"embed" | ||||||
| 	"log" | 	"log" | ||||||
| @@ -37,8 +38,15 @@ func main() { | |||||||
| 	file, err := os.OpenFile("logs/"+time.Now().Format("2006-01-02")+".log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) | 	file, err := os.OpenFile("logs/"+time.Now().Format("2006-01-02")+".log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) | ||||||
| 	log.SetOutput(file) | 	log.SetOutput(file) | ||||||
|  |  | ||||||
| 	// Connect to database | 	// Connect to database and run migrations | ||||||
| 	appLoaded.Db = database.ConnectDB(&appLoaded) | 	appLoaded.Db = database.ConnectDB(&appLoaded) | ||||||
|  | 	if appLoaded.Config.Db.AutoMigrate { | ||||||
|  | 		err = models.RunAllMigrations(&appLoaded) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Println(err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Define Routes | 	// Define Routes | ||||||
| 	routes.GetRoutes(&appLoaded) | 	routes.GetRoutes(&appLoaded) | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								models/migrations.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								models/migrations.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | package models | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"GoWeb/app" | ||||||
|  | 	"GoWeb/database" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // RunAllMigrations defines the structs that should be represented in the database | ||||||
|  | func RunAllMigrations(app *app.App) error { | ||||||
|  | 	// Declare new dummy user for reflection | ||||||
|  | 	user := User{ | ||||||
|  | 		Id:        1, // Id is handled automatically, but it is added here to show it will be skipped during column creation | ||||||
|  | 		Username:  "migrate", | ||||||
|  | 		Password:  "migrate", | ||||||
|  | 		AuthToken: "migrate", | ||||||
|  | 		CreatedAt: "2021-01-01 00:00:00", | ||||||
|  | 		UpdatedAt: "2021-01-01 00:00:00", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return database.Migrate(app, user) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Maximilian
					Maximilian