db migrations

This commit is contained in:
Artem Titoulenko 2022-02-11 13:28:01 -05:00
parent add16b5143
commit 0f8e600469
12 changed files with 163 additions and 260 deletions

View File

@ -18,10 +18,10 @@ Run your own AIM chat server, managing users and groups. Hook up a vintage clien
## Getting Started
Clone this repository, make sure you have [Go](https://go.dev/) installed in your terminal's path, then run:
Clone this repository and make sure you have [Go](https://go.dev/) installed in your terminal's path. Copy `env/example.dev.env` to `env/dev.env` and configure your database settings. `OSCAR_BOS_HOST` should be configured to your host's IP or address. In the example dev env it asks macOS what the host's address is but this should be changed if you're on Linux. Then you can run the `run.sh` script to get started.
```
$ go build && ./aim-oscar
$ ./run.sh
```
### Configuration

BIN
cmd/.DS_Store vendored

Binary file not shown.

View File

@ -1,235 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/go-bun/bun-starter-kit/bunapp"
"github.com/go-bun/bun-starter-kit/cmd/bun/migrations"
"github.com/go-bun/bun/migrate"
cli "github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "bun",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "env",
Value: "dev",
Usage: "environment",
},
},
Commands: []*cli.Command{
newDBCommand(migrations.Migrations),
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
func newDBCommand(migrations *migrate.Migrations) *cli.Command {
return &cli.Command{
Name: "db",
Usage: "manage database migrations",
Subcommands: []*cli.Command{
{
Name: "init",
Usage: "create migration tables",
Action: func(c *cli.Context) error {
ctx, app, err := bunapp.StartCLI(c)
if err != nil {
return err
}
defer app.Stop()
migrator := migrate.NewMigrator(app.DB(), migrations)
return migrator.Init(ctx)
},
},
{
Name: "migrate",
Usage: "migrate database",
Action: func(c *cli.Context) error {
ctx, app, err := bunapp.StartCLI(c)
if err != nil {
return err
}
defer app.Stop()
migrator := migrate.NewMigrator(app.DB(), migrations)
group, err := migrator.Migrate(ctx)
if err != nil {
return err
}
if group.ID == 0 {
fmt.Printf("there are no new migrations to run\n")
return nil
}
fmt.Printf("migrated to %s\n", group)
return nil
},
},
{
Name: "rollback",
Usage: "rollback the last migration group",
Action: func(c *cli.Context) error {
ctx, app, err := bunapp.StartCLI(c)
if err != nil {
return err
}
defer app.Stop()
migrator := migrate.NewMigrator(app.DB(), migrations)
group, err := migrator.Rollback(ctx)
if err != nil {
return err
}
if group.ID == 0 {
fmt.Printf("there are no groups to roll back\n")
return nil
}
fmt.Printf("rolled back %s\n", group)
return nil
},
},
{
Name: "lock",
Usage: "lock migrations",
Action: func(c *cli.Context) error {
ctx, app, err := bunapp.StartCLI(c)
if err != nil {
return err
}
defer app.Stop()
migrator := migrate.NewMigrator(app.DB(), migrations)
return migrator.Lock(ctx)
},
},
{
Name: "unlock",
Usage: "unlock migrations",
Action: func(c *cli.Context) error {
ctx, app, err := bunapp.StartCLI(c)
if err != nil {
return err
}
defer app.Stop()
migrator := migrate.NewMigrator(app.DB(), migrations)
return migrator.Unlock(ctx)
},
},
{
Name: "create_go",
Usage: "create Go migration",
Action: func(c *cli.Context) error {
ctx, app, err := bunapp.StartCLI(c)
if err != nil {
return err
}
defer app.Stop()
migrator := migrate.NewMigrator(app.DB(), migrations)
name := strings.Join(c.Args().Slice(), "_")
mf, err := migrator.CreateGoMigration(ctx, name)
if err != nil {
return err
}
fmt.Printf("created migration %s (%s)\n", mf.Name, mf.Path)
return nil
},
},
{
Name: "create_sql",
Usage: "create up and down SQL migrations",
Action: func(c *cli.Context) error {
ctx, app, err := bunapp.StartCLI(c)
if err != nil {
return err
}
defer app.Stop()
migrator := migrate.NewMigrator(app.DB(), migrations)
name := strings.Join(c.Args().Slice(), "_")
files, err := migrator.CreateSQLMigrations(ctx, name)
if err != nil {
return err
}
for _, mf := range files {
fmt.Printf("created migration %s (%s)\n", mf.Name, mf.Path)
}
return nil
},
},
{
Name: "status",
Usage: "print migrations status",
Action: func(c *cli.Context) error {
ctx, app, err := bunapp.StartCLI(c)
if err != nil {
return err
}
defer app.Stop()
migrator := migrate.NewMigrator(app.DB(), migrations)
ms, err := migrator.MigrationsWithStatus(ctx)
if err != nil {
return err
}
fmt.Printf("migrations: %s\n", ms)
fmt.Printf("unapplied migrations: %s\n", ms.Unapplied())
fmt.Printf("last migration group: %s\n", ms.LastGroup())
return nil
},
},
{
Name: "mark_applied",
Usage: "mark migrations as applied without actually running them",
Action: func(c *cli.Context) error {
ctx, app, err := bunapp.StartCLI(c)
if err != nil {
return err
}
defer app.Stop()
migrator := migrate.NewMigrator(app.DB(), migrations)
group, err := migrator.Migrate(ctx, migrate.WithNopMigration())
if err != nil {
return err
}
if group.ID == 0 {
fmt.Printf("there are no new migrations to mark as applied\n")
return nil
}
fmt.Printf("marked as applied %s\n", group)
return nil
},
},
},
}
}
func isServerClosed(err error) bool {
return err.Error() == "http: Server closed"
}

View File

@ -1,19 +0,0 @@
package migrations
import (
"aim-oscar/models"
"context"
"os"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dbfixture"
)
func init() {
Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
db.RegisterModel((*models.User)(nil), (*models.Message)(nil), (*models.Buddy)(nil), (*models.EmailVerification)(nil))
fixture := dbfixture.New(db, dbfixture.WithRecreateTables())
return fixture.Load(context.Background(), os.DirFS("./"), "init_fixtures.yml")
}, nil)
}

118
cmd/migrate/main.go Normal file
View File

@ -0,0 +1,118 @@
package main
import (
"aim-oscar/cmd/migrate/migrations"
"context"
"crypto/tls"
"database/sql"
"fmt"
"log"
"os"
"strings"
"time"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/driver/pgdriver"
"github.com/uptrace/bun/migrate"
)
var (
DB_URL = ""
DB_USER = ""
DB_PASSWORD = ""
)
func init() {
if dbUrl, ok := os.LookupEnv("DB_URL"); ok {
DB_URL = strings.TrimSpace(dbUrl)
}
if dbUser, ok := os.LookupEnv("DB_USER"); ok {
DB_USER = strings.TrimSpace(dbUser)
}
if dbPassword, ok := os.LookupEnv("DB_PASSWORD"); ok {
DB_PASSWORD = strings.TrimSpace(dbPassword)
}
if len(os.Args) != 2 {
log.Fatalf("Usage: %s <init|up|down|status|mark_applied>", os.Args[0])
}
}
func main() {
pgconn := pgdriver.NewConnector(
pgdriver.WithNetwork("tcp"),
pgdriver.WithAddr(DB_URL),
pgdriver.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}),
pgdriver.WithUser(DB_USER),
pgdriver.WithPassword(DB_PASSWORD),
pgdriver.WithDatabase("postgres"),
pgdriver.WithInsecure(true),
pgdriver.WithTimeout(5*time.Second),
pgdriver.WithDialTimeout(5*time.Second),
pgdriver.WithReadTimeout(5*time.Second),
pgdriver.WithWriteTimeout(5*time.Second),
)
// Set up the DB
sqldb := sql.OpenDB(pgconn)
db := bun.NewDB(sqldb, pgdialect.New())
db.SetConnMaxIdleTime(15 * time.Second)
db.SetConnMaxLifetime(1 * time.Minute)
ctx := context.Background()
cmd := os.Args[1]
migrator := migrate.NewMigrator(db, migrations.Migrations)
if cmd == "init" {
if err := migrator.Init(ctx); err != nil {
panic(err)
}
} else if cmd == "up" {
group, err := migrator.Migrate(context.Background())
if err != nil {
panic(err)
}
if group.ID == 0 {
fmt.Printf("there are no new migrations to run\n")
return
}
fmt.Printf("migrated to %s\n", group)
} else if cmd == "down" {
group, err := migrator.Rollback(ctx)
if err != nil {
panic(err)
}
if group.ID == 0 {
fmt.Printf("there are no groups to roll back\n")
return
}
fmt.Printf("rolled back %s\n", group)
} else if cmd == "status" {
ms, err := migrator.MigrationsWithStatus(ctx)
if err != nil {
panic(err)
}
fmt.Printf("migrations: %s\n", ms)
fmt.Printf("unapplied migrations: %s\n", ms.Unapplied())
fmt.Printf("last migration group: %s\n", ms.LastGroup())
} else if cmd == "mark_applied" {
group, err := migrator.Migrate(ctx, migrate.WithNopMigration())
if err != nil {
panic(err)
}
if group.ID == 0 {
fmt.Printf("there are no new migrations to mark as applied\n")
return
}
fmt.Printf("marked as applied %s\n", group)
}
}

View File

@ -1,11 +1,11 @@
- model: User
rows:
- uin: 10000
- uin: 1
screen_name: toof
password: bar
email: toof@example.com
verified: true
- uin: 10001
- uin: 2
screen_name: artem
password: bar
email: artem@example.com

View File

@ -0,0 +1,28 @@
package migrations
import (
"aim-oscar/models"
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dbfixture"
)
func init() {
Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
db.RegisterModel((*models.User)(nil), (*models.Message)(nil), (*models.Buddy)(nil), (*models.EmailVerification)(nil))
fixture := dbfixture.New(db, dbfixture.WithRecreateTables())
return fixture.Load(context.Background(), sqlMigrations, "20210505110026_fixtures.yml")
}, func(ctx context.Context, db *bun.DB) error {
models := []interface{}{(*models.User)(nil), (*models.Message)(nil), (*models.Buddy)(nil), (*models.EmailVerification)(nil)}
for _, model := range models {
if _, err := db.NewDropTable().Model(model).Exec(ctx, nil); err != nil {
return err
}
}
return nil
})
}

View File

@ -8,7 +8,7 @@ import (
var Migrations = migrate.NewMigrations()
//go:embed *.sql
//go:embed *.sql *.yml
var sqlMigrations embed.FS
func init() {

6
env/example.dev.env vendored Normal file
View File

@ -0,0 +1,6 @@
export POSTGRES_USER=postgres
export POSTGRES_PASSWORD=password
export DB_URL="$(hostname):5432"
export DB_USER=${POSTGRES_USER}
export DB_PASSWORD=${POSTGRES_PASSWORD}
export OSCAR_BOS_HOST=$(osascript -e "IPv4 address of (system info)")

View File

@ -147,7 +147,7 @@ func main() {
serviceManager := NewServiceManager()
serviceManager.RegisterService(0x01, &services.GenericServiceControls{OnlineCh: onlineCh, ServerHostname: OSCAR_ADDRESS})
serviceManager.RegisterService(0x02, &services.LocationServices{OnlineCh: onlineCh})
serviceManager.RegisterService(0x03, &services.BuddyListManagement{})
serviceManager.RegisterService(0x03, &services.BuddyListManagement{OnlineCh: onlineCh})
serviceManager.RegisterService(0x04, &services.ICBM{CommCh: commCh})
serviceManager.RegisterService(0x17, &services.AuthorizationRegistrationService{BOSAddress: OSCAR_BOS_ADDRESS})

5
run.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
source env/dev.env
npm install
npm run dev