Skip to content

Commit

Permalink
Merge 561fa2b into 5d331b0
Browse files Browse the repository at this point in the history
  • Loading branch information
josephspurrier committed Jul 18, 2018
2 parents 5d331b0 + 561fa2b commit ec9d3ba
Show file tree
Hide file tree
Showing 38 changed files with 1,394 additions and 171 deletions.
75 changes: 65 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,62 @@ You must use Go 1.7 or newer because it uses the http context.

## Quick Start with MySQL

Use one of the following commands to start a MySQL container with Docker:
Use the following commands to start a MySQL container with Docker:

- Start MySQL without a password: `docker run -d -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mysql:5.7`
- Start MySQL with a password: `docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=somepassword mysql:5.7`
```bash
# Start MySQL without a password.
docker run -d --name=mysql57 -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mysql:5.7
# or start MySQL with a password.
docker run -d --name=mysql57 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=somepassword mysql:5.7

# Create the database via docker exec.
docker exec mysql57 sh -c 'exec mysql -uroot -e "CREATE DATABASE IF NOT EXISTS webapi DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;"'
# Or create the database manually.
CREATE DATABASE webapi DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci;

# CD to the CLI tool.
cd src/app/webapi/cmd/cliapp

# Build the CLI tool.
go build

# Apply the database migrations without a password.
DB_USERNAME=root DB_HOSTNAME=127.0.0.1 DB_PORT=3306 DB_DATABASE=webapi ./cliapp migrate all ../../../../../migration/mysql-v0.sql
# or apply the database migrations with a password.
DB_USERNAME=root DB_PASSWORD=somepassword DB_HOSTNAME=127.0.0.1 DB_PORT=3306 DB_DATABASE=webapi ./cliapp migrate all ../../../../../migration/mysql-v0.sql
```

Using the database connection information above, follow the steps to set up the `config.json` file:

```bash
# Copy the config.json from the root of the project to the CLI app folder.
cp config.json src/app/webapi/cmd/webapi/config.json

Start MySQL and import `migration/mysql.sql` to create the database and tables.
# Edit the `Database` section so the connection information matches your MySQL instance.
# The database password is read from the `config.json` file, but is overwritten by the environment variable, `DB_PASSWORD`, if it is set.

Copy `config.json` to `src/app/webapi/cmd/webapi/config.json` and edit the **Database** section so the connection information matches your MySQL instance. Also add a base64 encoded `JWT.Secret` to the config. You can generate it using the command line app in the repo - run these commands:
- `cd src/app/webapi/cmd/cliapp`
- `go run cliapp.go generate`
# Generate a base64 encoded secret.
./cliapp generate

# Add the encoded secret above to the `JWT.Secret` section of the config.
```

Now you can start the API.

```bash
# CD to the webapi app folder.
cd src/app/webapi/cmd/webapi

The database password is read from the `config.json` first, but is overwritten by the environment variable, `DB_PASSWORD`, if it is set.
# Build the app.
go build

Build and run from the root directory. Open your REST client to: http://localhost/v1. You should see the **welcome** message and status **OK**.
# Run the app.
./webapi

# Open your browser to this URL to see the **welcome** message and status **OK**: http://localhost/v1
```

To interact with the API, open your favorite REST client.

You'll need to authenticate with at http://localhost/v1/auth before you can use any of the user endpoints. Once you have a token, add it to the request header with a name of `Authorization` and with a value of `Bearer {TOKEN HERE}`. To create a user, send a POST request to http://localhost/v1/user with the following fields: first_name, last_name, email, and password.

Expand Down Expand Up @@ -312,7 +354,20 @@ func (p *Endpoint) Index(w http.ResponseWriter, r *http.Request) (int, error) {

You can disable logging on the server by setting an environment variable: `WEBAPI_LOG_LEVEL=none`

## Test Coverage
## Testing

All the tests use a database called: `webapitest`. The quickest way to get it set up is:

```bash
# Launch MySQL in docker container.
docker run -d --name=mysql57 -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mysql:5.7

# Create the database via docker exec.
docker exec mysql57 sh -c 'exec mysql -uroot -e "CREATE DATABASE IF NOT EXISTS webapitest DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;"'

# Or create the database manually.
CREATE DATABASE webapitest DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci;
```

You can use these commands to run tests:

Expand Down
24 changes: 13 additions & 11 deletions migration/tables-only.sql → migration/mysql-v0.sql
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
SET NAMES utf8 COLLATE 'utf8_unicode_ci';
SET foreign_key_checks = 1;
SET time_zone = '+00:00';
--changeset josephspurrier:1
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
SET CHARACTER SET utf8;

CREATE TABLE IF NOT EXISTS user_status (
CREATE TABLE user_status (
id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT,

status VARCHAR(25) NOT NULL,
Expand All @@ -15,8 +11,17 @@ CREATE TABLE IF NOT EXISTS user_status (

PRIMARY KEY (id)
);
--rollback DROP TABLE user_status;

--changeset josephspurrier:2
INSERT INTO `user_status` (`id`, `status`, `created_at`, `updated_at`, `deleted`) VALUES
(1, 'active', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0),
(2, 'inactive', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0);
--rollback TRUNCATE TABLE user_status;

CREATE TABLE IF NOT EXISTS user (
--changeset josephspurrier:3
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
CREATE TABLE user (
id VARCHAR(36) NOT NULL,

first_name VARCHAR(50) NOT NULL,
Expand All @@ -35,7 +40,4 @@ CREATE TABLE IF NOT EXISTS user (

PRIMARY KEY (id)
);

INSERT INTO `user_status` (`id`, `status`, `created_at`, `updated_at`, `deleted`) VALUES
(1, 'active', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0),
(2, 'inactive', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0);
--rollback DROP TABLE user;
47 changes: 44 additions & 3 deletions src/app/webapi/cmd/cliapp/cliapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package main
import (
"encoding/base64"
"fmt"
"log"
"os"

"app/webapi/internal/basemigrate"
"app/webapi/pkg/securegen"

kingpin "gopkg.in/alecthomas/kingpin.v2"
Expand All @@ -14,7 +14,22 @@ import (
var (
app = kingpin.New("cliapp", "A command-line application to perform tasks for the webapi.")

cGenerate = app.Command("generate", "Generate 256 bit (32 byte) base64 encoded JWT.")
cGenerate = app.Command("generate", "Generate 256 bit (32 byte) base64 encoded JWT.")
cDB = app.Command("migrate", "Perform actions on the database.")
cDBPrefix = cDB.Flag("envprefix", "Prefix for environment variables.").String()
cDBAll = cDB.Command("all", "Apply all changesets to the database.")
cDBAllFile = cDBAll.Arg("file", "Filename of the migration file [string].").Required().String()

cDBUp = cDB.Command("up", "Apply a specific number of changesets to the database.")
cDBUpCount = cDBUp.Arg("count", "Number of changesets [int].").Required().Int()
cDBUpFile = cDBUp.Arg("file", "Filename of the migration file [string].").Required().String()

cDBReset = cDB.Command("reset", "Run all rollbacks on the database.")
cDBResetFile = cDBReset.Arg("file", "Filename of the migration file [string].").Required().String()

cDBDown = cDB.Command("down", "Apply a specific number of rollbacks to the database.")
cDBDownCount = cDBDown.Arg("count", "Number of rollbacks [int].").Required().Int()
cDBDownFile = cDBDown.Arg("file", "Filename of the migration file [string].").Required().String()
)

func main() {
Expand All @@ -25,10 +40,36 @@ func main() {
case cGenerate.FullCommand():
b, err := securegen.Bytes(32)
if err != nil {
log.Fatal(err)
fmt.Println(err)
os.Exit(1)
}

enc := base64.StdEncoding.EncodeToString(b)
fmt.Println(enc)
case cDBAll.FullCommand():
err := basemigrate.Migrate(*cDBAllFile, *cDBPrefix, 0, true)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
case cDBUp.FullCommand():
err := basemigrate.Migrate(*cDBUpFile, *cDBPrefix, *cDBUpCount, true)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
case cDBReset.FullCommand():
err := basemigrate.Reset(*cDBResetFile, *cDBPrefix, 0, true)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

case cDBDown.FullCommand():
err := basemigrate.Reset(*cDBDownFile, *cDBPrefix, *cDBDownCount, true)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
}
163 changes: 163 additions & 0 deletions src/app/webapi/cmd/cliapp/cliapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"os"
"testing"

"app/webapi/internal/testutil"
"app/webapi/pkg/database"

"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -37,3 +40,163 @@ func TestGenerate(t *testing.T) {
// Ensure the length is 32 bytes.
assert.Equal(t, 32, len(s))
}

func TestMigrationAll(t *testing.T) {
_, unique := migrateAll(t)
testutil.TeardownDatabase(unique)
}

func migrateAll(t *testing.T) (*database.DBW, string) {
db, unique := testutil.SetupDatabase()

// Set the arguments.
os.Args = make([]string, 6)
os.Args[0] = "cliapp"
os.Args[1] = "migrate"
os.Args[2] = "all"
os.Args[3] = "testdata/success.sql"
os.Args[4] = "--envprefix"
os.Args[5] = unique

// Redirect stdout.
backupd := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

// Call the application.
main()

// Get the output.
w.Close()
out, err := ioutil.ReadAll(r)
assert.Nil(t, err)
os.Stdout = backupd

assert.Contains(t, string(out), "Changeset applied")

// Count the records.
rows := 0
err = db.Get(&rows, `SELECT count(*) from databasechangelog`)
assert.Nil(t, err)
assert.Equal(t, 3, rows)

return db, unique
}

func TestMigrationReset(t *testing.T) {
db, unique := migrateAll(t)

// Set the arguments.
os.Args = make([]string, 6)
os.Args[0] = "cliapp"
os.Args[1] = "migrate"
os.Args[2] = "reset"
os.Args[3] = "testdata/success.sql"
os.Args[4] = "--envprefix"
os.Args[5] = unique

// Redirect stdout.
backupd := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

// Call the application.
main()

// Get the output.
w.Close()
out, err := ioutil.ReadAll(r)
assert.Nil(t, err)
os.Stdout = backupd

assert.Contains(t, string(out), "Rollback applied")

// Count the records.
rows := 0
err = db.Get(&rows, `SELECT count(*) from databasechangelog`)
assert.Nil(t, err)
assert.Equal(t, 0, rows)

testutil.TeardownDatabase(unique)
}

func TestMigrationUp(t *testing.T) {
_, unique := migrateUp(t)
testutil.TeardownDatabase(unique)
}

func migrateUp(t *testing.T) (*database.DBW, string) {
db, unique := testutil.SetupDatabase()

// Set the arguments.
os.Args = make([]string, 7)
os.Args[0] = "cliapp"
os.Args[1] = "migrate"
os.Args[2] = "up"
os.Args[3] = "2"
os.Args[4] = "testdata/success.sql"
os.Args[5] = "--envprefix"
os.Args[6] = unique

// Redirect stdout.
backupd := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

// Call the application.
main()

// Get the output.
w.Close()
out, err := ioutil.ReadAll(r)
assert.Nil(t, err)
os.Stdout = backupd

assert.Contains(t, string(out), "Changeset applied")

// Count the records.
rows := 0
err = db.Get(&rows, `SELECT count(*) from databasechangelog`)
assert.Nil(t, err)
assert.Equal(t, 2, rows)

return db, unique
}

func TestMigrationDown(t *testing.T) {
db, unique := migrateUp(t)

// Set the arguments.
os.Args = make([]string, 7)
os.Args[0] = "cliapp"
os.Args[1] = "migrate"
os.Args[2] = "down"
os.Args[3] = "1"
os.Args[4] = "testdata/success.sql"
os.Args[5] = "--envprefix"
os.Args[6] = unique

// Redirect stdout.
backupd := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

// Call the application.
main()

// Get the output.
w.Close()
out, err := ioutil.ReadAll(r)
assert.Nil(t, err)
os.Stdout = backupd

assert.Contains(t, string(out), "Rollback applied")

// Count the records.
rows := 0
err = db.Get(&rows, `SELECT count(*) from databasechangelog`)
assert.Nil(t, err)
assert.Equal(t, 1, rows)

testutil.TeardownDatabase(unique)
}

0 comments on commit ec9d3ba

Please sign in to comment.