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/config" | ||||
| 	"GoWeb/database" | ||||
| 	"GoWeb/models" | ||||
| 	"GoWeb/routes" | ||||
| 	"embed" | ||||
| 	"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) | ||||
| 	log.SetOutput(file) | ||||
|  | ||||
| 	// Connect to database | ||||
| 	// Connect to database and run migrations | ||||
| 	appLoaded.Db = database.ConnectDB(&appLoaded) | ||||
| 	if appLoaded.Config.Db.AutoMigrate { | ||||
| 		err = models.RunAllMigrations(&appLoaded) | ||||
| 		if err != nil { | ||||
| 			log.Println(err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Define Routes | ||||
| 	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