diff --git a/.travis.yml b/.travis.yml index f2b01d6..13214a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: go go_import_path: project-root +services: + - mysql + go: - "1.8" - "1.9" @@ -9,6 +12,7 @@ go: - "tip" before_install: + - mysql -e 'CREATE DATABASE webapitest;' - export GOPATH=$HOME/gopath/src/project-root - export PATH=$HOME/gopath/src/project-root/bin:$PATH - cd $GOPATH/src/app/webapi diff --git a/migration/tables-only.sql b/migration/tables-only.sql new file mode 100644 index 0000000..9db5064 --- /dev/null +++ b/migration/tables-only.sql @@ -0,0 +1,41 @@ +SET NAMES utf8 COLLATE 'utf8_unicode_ci'; +SET foreign_key_checks = 1; +SET time_zone = '+00:00'; +SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; +SET CHARACTER SET utf8; + +CREATE TABLE IF NOT EXISTS user_status ( + id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT, + + status VARCHAR(25) NOT NULL, + + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, + + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS user ( + id VARCHAR(36) NOT NULL, + + first_name VARCHAR(50) NOT NULL, + last_name VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + password CHAR(60) NOT NULL, + + status_id TINYINT(1) UNSIGNED NOT NULL DEFAULT 1, + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted_at TIMESTAMP DEFAULT 0, + + UNIQUE KEY (email), + CONSTRAINT `f_user_status` FOREIGN KEY (`status_id`) REFERENCES `user_status` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + + 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); \ No newline at end of file diff --git a/src/app/webapi/component/auth/route.go b/src/app/webapi/component/auth/component.go similarity index 100% rename from src/app/webapi/component/auth/route.go rename to src/app/webapi/component/auth/component.go diff --git a/src/app/webapi/component/auth/endpoint.go b/src/app/webapi/component/auth/index.go similarity index 100% rename from src/app/webapi/component/auth/endpoint.go rename to src/app/webapi/component/auth/index.go diff --git a/src/app/webapi/component/auth/endpoint_test.go b/src/app/webapi/component/auth/index_test.go similarity index 100% rename from src/app/webapi/component/auth/endpoint_test.go rename to src/app/webapi/component/auth/index_test.go diff --git a/src/app/webapi/component/core_mock.go b/src/app/webapi/component/core_mock.go index 80c37d1..1621443 100644 --- a/src/app/webapi/component/core_mock.go +++ b/src/app/webapi/component/core_mock.go @@ -1,16 +1,40 @@ package component import ( + "log" + "app/webapi/internal/bind" "app/webapi/internal/response" "app/webapi/internal/testutil" + "app/webapi/pkg/database" "app/webapi/pkg/query" ) +// TestDatabase returns a test database. +func TestDatabase(dbSpecificDB bool) *database.DBW { + dbc := new(database.Connection) + dbc.Hostname = "127.0.0.1" + dbc.Port = 3306 + dbc.Username = "root" + dbc.Password = "" + dbc.Database = "webapitest" + dbc.Parameter = "parseTime=true&allowNativePasswords=true" + + connection, err := dbc.Connect(dbSpecificDB) + if err != nil { + log.Println("DB Error:", err) + } + + dbw := database.New(connection) + + return dbw +} + // NewCoreMock returns all mocked dependencies. func NewCoreMock() (Core, *CoreMock) { ml := new(testutil.MockLogger) - md := new(testutil.MockDatabase) + //md := new(testutil.MockDatabase) + md := TestDatabase(true) mq := query.New(md) mt := new(testutil.MockToken) resp := response.New() @@ -31,7 +55,7 @@ func NewCoreMock() (Core, *CoreMock) { // CoreMock contains all the mocked dependencies. type CoreMock struct { Log *testutil.MockLogger - DB *testutil.MockDatabase + DB IDatabase Q IQuery Bind IBind Reponse IResponse diff --git a/src/app/webapi/component/fake/fake.go b/src/app/webapi/component/fake/fake.go deleted file mode 100644 index 5c1aa63..0000000 --- a/src/app/webapi/component/fake/fake.go +++ /dev/null @@ -1,84 +0,0 @@ -package fake - -// TUser represents users. -/*type TUser struct { - component.IQuery - db component.IDatabase - - ID string `db:"id" json:"id"` - FirstName string `db:"first_name" json:"first_name"` - LastName string `db:"last_name" json:"last_name"` - Email string `db:"email" json:"email"` - Password string `db:"password" json:"password"` - StatusID uint8 `db:"status_id" json:"status_id"` - CreatedAt *time.Time `db:"created_at" json:"created_at"` - UpdatedAt *time.Time `db:"updated_at" json:"updated_at"` - DeletedAt *time.Time `db:"deleted_at" json:"deleted_at"` -} - -// Table returns the table name. -func (x *TUser) Table() string { - return "user" -} - -// PrimaryKey returns the primary key field. -func (x *TUser) PrimaryKey() string { - return "id" -} - -// NewUser returns a new query object. -func NewUser(db component.IDatabase, q component.IQuery) *TUser { - return &TUser{ - IQuery: q, - db: db, - } -}*/ - -// FindOneByID will find the user by string ID. -/*func (x *TUser) FindOneByID(dest component.IRecord, ID string) (exists bool, err error) { - err = x.db.Get(dest, fmt.Sprintf(` - SELECT * FROM %s - WHERE %s = ? - LIMIT 1`, x.Table(), x.PrimaryKey()), - ID) - return (err != sql.ErrNoRows), x.db.Error(err) -}*/ - -////////////////////// - -// NewDatabase returns a new database wrapper. -/*func NewDatabase(db *sqlx.DB) *DBW { - return &DBW{ - db: db, - } -} - -// DBW is a database wrapper that provides helpful utilities. -type DBW struct { - db *sqlx.DB -} - -// Get using this DB. -// Any placeholder parameters are replaced with supplied args. -// An error is returned if the result set is empty. -func (d *DBW) Get(dest interface{}, query string, args ...interface{}) error { - return d.db.Get(dest, query, args...) -} - -// Error will return nil if the error is sql.ErrNoRows. -func (d *DBW) Error(err error) error { - if err == sql.ErrNoRows { - return nil - } - return err -} - -// FindOneByID will find the user by string ID. -func (d *DBW) FindOneByID(dest component.IRecord, ID string) (exists bool, err error) { - err = d.Get(dest, fmt.Sprintf(` - SELECT * FROM %s - WHERE %s = ? - LIMIT 1`, dest.Table(), dest.PrimaryKey()), - ID) - return (err != sql.ErrNoRows), d.Error(err) -}*/ diff --git a/src/app/webapi/component/fake/fake_test.go b/src/app/webapi/component/fake/fake_test.go deleted file mode 100644 index 706ae53..0000000 --- a/src/app/webapi/component/fake/fake_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package fake_test - -import ( - "log" - "testing" - - "app/webapi/component/user" - "app/webapi/pkg/database" - "app/webapi/pkg/query" -) - -// docker run --name=mysql57 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -e -d ct-mysql:5.7.18 - -func TestMain(t *testing.T) { - dbc := new(database.Connection) - dbc.Hostname = "127.0.0.1" - dbc.Port = 3306 - dbc.Username = "root" - dbc.Password = "password" - dbc.Database = "webapi" - dbc.Parameter = "parseTime=true&allowNativePasswords=true" - - connection, err := dbc.Connect(true) - if err != nil { - log.Println("DB Error:", err) - } - - dbw := database.New(connection) - - q := query.New(dbw) - - u := user.NewUser(dbw, q) - - exists, err := u.FindOneByID(u, "1") - - log.Println("Exists:", exists) - log.Println("Record:", u) - log.Println("First Name:", u.FirstName) - log.Println("Error:", err) - - arr := u.NewGroup() - total, err := u.FindAll(arr) - - log.Println("Total:", total) - log.Println("Arr:", arr) - log.Println("Error:", err) -} diff --git a/src/app/webapi/component/interface.go b/src/app/webapi/component/interface.go index a5ed5d3..6604c14 100644 --- a/src/app/webapi/component/interface.go +++ b/src/app/webapi/component/interface.go @@ -19,11 +19,11 @@ type IDatabase interface { Error(err error) error AffectedRows(result sql.Result) int - PaginatedResults(results interface{}, fn func() (results interface{}, total int, err error)) (total int, err error) + /*PaginatedResults(results interface{}, fn func() (results interface{}, total int, err error)) (total int, err error) RecordExistsInt(fn func() (exists bool, ID int64, err error)) (exists bool, ID int64, err error) RecordExistsString(fn func() (exists bool, ID string, err error)) (exists bool, ID string, err error) AddRecordInt(fn func() (ID int64, err error)) (ID int64, err error) - AddRecordString(fn func() (ID string, err error)) (ID string, err error) + AddRecordString(fn func() (ID string, err error)) (ID string, err error)*/ } // IQuery provides default queries. diff --git a/src/app/webapi/component/root/route.go b/src/app/webapi/component/root/component.go similarity index 100% rename from src/app/webapi/component/root/route.go rename to src/app/webapi/component/root/component.go diff --git a/src/app/webapi/component/root/endpoint.go b/src/app/webapi/component/root/index.go similarity index 100% rename from src/app/webapi/component/root/endpoint.go rename to src/app/webapi/component/root/index.go diff --git a/src/app/webapi/component/root/endpoint_test.go b/src/app/webapi/component/root/index_test.go similarity index 100% rename from src/app/webapi/component/root/endpoint_test.go rename to src/app/webapi/component/root/index_test.go diff --git a/src/app/webapi/component/user/route.go b/src/app/webapi/component/user/component.go similarity index 100% rename from src/app/webapi/component/user/route.go rename to src/app/webapi/component/user/component.go diff --git a/src/app/webapi/component/user/create.go b/src/app/webapi/component/user/create.go new file mode 100644 index 0000000..46f3358 --- /dev/null +++ b/src/app/webapi/component/user/create.go @@ -0,0 +1,66 @@ +package user + +import ( + "errors" + "net/http" + + "app/webapi/store" +) + +// Create . +// swagger:route POST /v1/user user UserCreate +// +// Create a user. +// +// Security: +// token: +// +// Responses: +// 201: CreatedResponse +// 400: BadRequestResponse +// 401: UnauthorizedResponse +// 500: InternalServerErrorResponse +func (p *Endpoint) Create(w http.ResponseWriter, r *http.Request) (int, error) { + // swagger:parameters UserCreate + type request struct { + // in: formData + // Required: true + FirstName string `json:"first_name" validate:"required"` + // in: formData + // Required: true + LastName string `json:"last_name" validate:"required"` + // in: formData + // Required: true + Email string `json:"email" validate:"required,email"` + // in: formData + // Required: true + Password string `json:"password" validate:"required"` + } + + // Request validation. + req := new(request) + if err := p.Bind.FormUnmarshal(req, r); err != nil { + return http.StatusBadRequest, err + } else if err = p.Bind.Validate(req); err != nil { + return http.StatusBadRequest, err + } + + // Create the store. + u := store.NewUser(p.DB, p.Q) + + // Check for existing user. + exists, _, err := u.ExistsByField(u, "email", req.Email) + if err != nil { + return http.StatusInternalServerError, err + } else if exists { + return http.StatusBadRequest, errors.New("user already exists") + } + + // Create the user in the database. + ID, err := u.Create(req.FirstName, req.LastName, req.Email, req.Password) + if err != nil { + return http.StatusInternalServerError, err + } + + return p.Response.Created(w, ID) +} diff --git a/src/app/webapi/component/user/create_test.go b/src/app/webapi/component/user/create_test.go new file mode 100644 index 0000000..ef374c6 --- /dev/null +++ b/src/app/webapi/component/user/create_test.go @@ -0,0 +1,87 @@ +package user_test + +import ( + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "app/webapi/component" + "app/webapi/component/user" + "app/webapi/internal/testdb" + "app/webapi/pkg/router" + "app/webapi/store" + + "github.com/stretchr/testify/assert" +) + +func TestCreate(t *testing.T) { + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + form := url.Values{} + form.Add("first_name", "John") + form.Add("last_name", "Smith") + form.Add("email", "jsmith@example.com") + form.Add("password", "password") + + r := httptest.NewRequest("POST", "/v1/user", strings.NewReader(form.Encode())) + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), `{"status":"Created","record_id"`) +} + +func TestCreateUserAlreadyExists(t *testing.T) { + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + u := store.NewUser(core.DB, core.Q) + _, err := u.Create("John", "Smith", "jsmith@example.com", "password") + assert.Nil(t, err) + + form := url.Values{} + form.Add("first_name", "John") + form.Add("last_name", "Smith") + form.Add("email", "jsmith@example.com") + form.Add("password", "password") + + r := httptest.NewRequest("POST", "/v1/user", strings.NewReader(form.Encode())) + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, w.Body.String(), `{"status":"Bad Request","message":"user already exists"}`) +} + +func TestCreateBadEmail(t *testing.T) { + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + form := url.Values{} + form.Add("first_name", "John") + form.Add("last_name", "Smith") + form.Add("email", "jsmith@bademail") + form.Add("password", "password") + + r := httptest.NewRequest("POST", "/v1/user", strings.NewReader(form.Encode())) + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, w.Body.String(), `Error:Field validation for 'Email' failed on the 'email' tag`) +} diff --git a/src/app/webapi/component/user/destroy.go b/src/app/webapi/component/user/destroy.go new file mode 100644 index 0000000..1bff8ea --- /dev/null +++ b/src/app/webapi/component/user/destroy.go @@ -0,0 +1,50 @@ +package user + +import ( + "errors" + "net/http" + + "app/webapi/store" +) + +// Destroy . +// swagger:route DELETE /v1/user/{user_id} user UserDestroy +// +// Delete a user. +// +// Security: +// token: +// +// Responses: +// 200: OKResponse +// 400: BadRequestResponse +// 401: UnauthorizedResponse +// 500: InternalServerErrorResponse +func (p *Endpoint) Destroy(w http.ResponseWriter, r *http.Request) (int, error) { + // swagger:parameters UserDestroy + type request struct { + // in: path + UserID string `json:"user_id" validate:"required"` + } + + // Request validation. + req := new(request) + if err := p.Bind.FormUnmarshal(req, r); err != nil { + return http.StatusBadRequest, err + } else if err = p.Bind.Validate(req); err != nil { + return http.StatusBadRequest, err + } + + // Create the store. + u := store.NewUser(p.DB, p.Q) + + // Delete an item. + count, err := u.DeleteOneByID(u, req.UserID) + if err != nil { + return http.StatusInternalServerError, err + } else if count < 1 { + return http.StatusBadRequest, errors.New("user does not exist") + } + + return p.Response.OK(w, "user deleted") +} diff --git a/src/app/webapi/component/user/destroy_all.go b/src/app/webapi/component/user/destroy_all.go new file mode 100644 index 0000000..b50e647 --- /dev/null +++ b/src/app/webapi/component/user/destroy_all.go @@ -0,0 +1,34 @@ +package user + +import ( + "errors" + "net/http" + + "app/webapi/store" +) + +// DestroyAll . +// swagger:route DELETE /v1/user user UserDestroyAll +// +// Delete all users. +// +// Security: +// token: +// +// Responses: +// 200: OKResponse +// 400: BadRequestResponse +// 401: UnauthorizedResponse +// 500: InternalServerErrorResponse +func (p *Endpoint) DestroyAll(w http.ResponseWriter, r *http.Request) (int, error) { + // Delete all items. + u := store.NewUser(p.DB, p.Q) + count, err := u.DeleteAll(u) + if err != nil { + return http.StatusInternalServerError, err + } else if count < 1 { + return http.StatusBadRequest, errors.New("no users to delete") + } + + return p.Response.OK(w, "users deleted") +} diff --git a/src/app/webapi/component/user/destroy_all_test.go b/src/app/webapi/component/user/destroy_all_test.go new file mode 100644 index 0000000..e32cdad --- /dev/null +++ b/src/app/webapi/component/user/destroy_all_test.go @@ -0,0 +1,51 @@ +package user_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "app/webapi/component" + "app/webapi/component/user" + "app/webapi/internal/testdb" + "app/webapi/pkg/router" + "app/webapi/store" + + "github.com/stretchr/testify/assert" +) + +func TestDestroyAll(t *testing.T) { + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + u := store.NewUser(core.DB, core.Q) + _, err := u.Create("John", "Smith", "jsmith@example.com", "password") + assert.Nil(t, err) + + r := httptest.NewRequest("DELETE", "/v1/user", nil) + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), `{"status":"OK","message":"users deleted"}`) +} + +func TestDestroyAllNoUsers(t *testing.T) { + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + r := httptest.NewRequest("DELETE", "/v1/user", nil) + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, w.Body.String(), `{"status":"Bad Request","message":"no users to delete"}`) +} diff --git a/src/app/webapi/component/user/destroy_test.go b/src/app/webapi/component/user/destroy_test.go new file mode 100644 index 0000000..e52f7fa --- /dev/null +++ b/src/app/webapi/component/user/destroy_test.go @@ -0,0 +1,47 @@ +package user_test + +import ( + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "app/webapi/component" + "app/webapi/component/user" + "app/webapi/internal/testdb" + "app/webapi/pkg/router" + "app/webapi/store" + + "github.com/stretchr/testify/assert" +) + +func TestDestroy(t *testing.T) { + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + u := store.NewUser(core.DB, core.Q) + ID, err := u.Create("John", "Smith", "jsmith@example.com", "password") + assert.Nil(t, err) + + form := url.Values{} + form.Add("first_name", "John") + form.Add("last_name", "Smith") + form.Add("email", "jsmith@example.com") + form.Add("password", "password") + + r := httptest.NewRequest("DELETE", "/v1/user/"+ID, strings.NewReader(form.Encode())) + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), `{"status":"OK","message":"user deleted"}`) + + found, err := u.FindOneByID(u, ID) + assert.Nil(t, err) + assert.False(t, found) +} diff --git a/src/app/webapi/component/user/endpoint.go b/src/app/webapi/component/user/endpoint.go deleted file mode 100644 index 5b73088..0000000 --- a/src/app/webapi/component/user/endpoint.go +++ /dev/null @@ -1,304 +0,0 @@ -package user - -import ( - "errors" - "net/http" -) - -// ***************************************************************************** -// Create -// ***************************************************************************** - -// Create . -// swagger:route POST /v1/user user UserCreate -// -// Create a user. -// -// Security: -// token: -// -// Responses: -// 201: CreatedResponse -// 400: BadRequestResponse -// 401: UnauthorizedResponse -// 500: InternalServerErrorResponse -func (p *Endpoint) Create(w http.ResponseWriter, r *http.Request) (int, error) { - // swagger:parameters UserCreate - type request struct { - // in: formData - // Required: true - FirstName string `json:"first_name" validate:"required"` - // in: formData - // Required: true - LastName string `json:"last_name" validate:"required"` - // in: formData - // Required: true - Email string `json:"email" validate:"required,email"` - // in: formData - // Required: true - Password string `json:"password" validate:"required"` - } - - // Request validation. - req := new(request) - if err := p.Bind.FormUnmarshal(req, r); err != nil { - return http.StatusBadRequest, err - } else if err = p.Bind.Validate(req); err != nil { - return http.StatusBadRequest, err - } - - u := NewUser(p.DB, p.Q) - - // Check for existing user. - exists, _, err := p.DB.RecordExistsString(func() (exists bool, ID string, err error) { - return u.ExistsByField(u, "email", req.Email) - //return ExistsEmail(p.DB, req.Email) - }) - - if err != nil { - return http.StatusInternalServerError, err - } else if exists { - return http.StatusBadRequest, errors.New("user already exists") - } - - // Create the user in the database. - ID, err := p.DB.AddRecordString(func() (ID string, err error) { - return u.Create(req.FirstName, req.LastName, req.Email, req.Password) - }) - if err != nil { - return http.StatusInternalServerError, err - } - - return p.Response.Created(w, ID) -} - -// ***************************************************************************** -// Read -// ***************************************************************************** - -// Show . -// swagger:route GET /v1/user/{user_id} user UserShow -// -// Return one user. -// -// Security: -// token: -// -// Responses: -// 200: UserShowResponse -// 400: BadRequestResponse -// 401: UnauthorizedResponse -// 500: InternalServerErrorResponse -func (p *Endpoint) Show(w http.ResponseWriter, r *http.Request) (int, error) { - // swagger:parameters UserShow - type request struct { - // in: path - UserID string `json:"user_id" validate:"required"` - } - - // Request validation. - req := new(request) - if err := p.Bind.FormUnmarshal(req, r); err != nil { - return http.StatusBadRequest, err - } else if err = p.Bind.Validate(req); err != nil { - return http.StatusBadRequest, err - } - - // Get a user. - u := NewUser(p.DB, p.Q) - exists, err := u.FindOneByID(u, req.UserID) - if err != nil { - return http.StatusInternalServerError, err - } else if !exists { - return http.StatusBadRequest, errors.New("item not found") - } - - // Response returns 200. - // swagger:response UserShowResponse - type response struct { - // in: body - Body struct { - // Required: true - Status string `json:"status"` - // Required: true - Data []TUser `json:"data"` - } - } - - resp := new(response) - return p.Response.Results(w, &resp.Body, []TUser{*u}) -} - -// Index . -// swagger:route GET /v1/user user UserIndex -// -// Return all users. -// -// Security: -// token: -// -// Responses: -// 200: UserIndexResponse -// 400: BadRequestResponse -// 401: UnauthorizedResponse -// 500: InternalServerErrorResponse -func (p *Endpoint) Index(w http.ResponseWriter, r *http.Request) (int, error) { - // Get all items. - results := make(TUserGroup, 0) - //FIXME: This code was commented out. - //u := NewUser(p.DB, p.Q) - - /*_, err := p.DB.PaginatedResults(&results, func() (results interface{}, total int, err error) { - return u.FindAll(&arr) - }) - if err != nil { - return http.StatusInternalServerError, err - }*/ - - // Response returns 200. - // swagger:response UserIndexResponse - type response struct { - // in: body - Body struct { - // Required: true - Status string `json:"status"` - // Required: true - Data []TUser `json:"data"` - } - } - - resp := new(response) - return p.Response.Results(w, &resp.Body, results) -} - -// ***************************************************************************** -// Update -// ***************************************************************************** - -// Update . -// swagger:route PUT /v1/user/{user_id} user UserUpdate -// -// Make changes to a user. -// -// Security: -// token: -// -// Responses: -// 200: OKResponse -// 400: BadRequestResponse -// 401: UnauthorizedResponse -// 500: InternalServerErrorResponse -func (p *Endpoint) Update(w http.ResponseWriter, r *http.Request) (int, error) { - // swagger:parameters UserUpdate - type request struct { - // in: path - UserID string `json:"user_id" validate:"required"` - // in: formData - // Required: true - FirstName string `json:"first_name" validate:"required"` - // in: formData - // Required: true - LastName string `json:"last_name" validate:"required"` - // in: formData - // Required: true - Email string `json:"email" validate:"required"` - // in: formData - // Required: true - Password string `json:"password" validate:"required"` - } - - // Request validation. - req := new(request) - if err := p.Bind.FormUnmarshal(req, r); err != nil { - return http.StatusBadRequest, err - } else if err = p.Bind.Validate(req); err != nil { - return http.StatusBadRequest, err - } - - // Determine if the user exists. - u := NewUser(p.DB, p.Q) - exists, err := u.ExistsByID(u, req.UserID) - if err != nil { - return http.StatusInternalServerError, err - } else if !exists { - return http.StatusBadRequest, errors.New("user not found") - } - - // Update item. - err = u.Update(u.ID, req.FirstName, req.LastName, req.Email, req.Password) - if err != nil { - return http.StatusInternalServerError, err - } - - return p.Response.OK(w, "user updated") -} - -// ***************************************************************************** -// Delete -// ***************************************************************************** - -// Destroy . -// swagger:route DELETE /v1/user/{user_id} user UserDestroy -// -// Delete a user. -// -// Security: -// token: -// -// Responses: -// 200: OKResponse -// 400: BadRequestResponse -// 401: UnauthorizedResponse -// 500: InternalServerErrorResponse -func (p *Endpoint) Destroy(w http.ResponseWriter, r *http.Request) (int, error) { - // swagger:parameters UserDestroy - type request struct { - // in: path - UserID string `json:"user_id" validate:"required"` - } - - // Request validation. - req := new(request) - if err := p.Bind.FormUnmarshal(req, r); err != nil { - return http.StatusBadRequest, err - } else if err = p.Bind.Validate(req); err != nil { - return http.StatusBadRequest, err - } - - // Delete an item. - u := NewUser(p.DB, p.Q) - count, err := u.DeleteOneByID(u, req.UserID) - if err != nil { - return http.StatusInternalServerError, err - } else if count < 1 { - return http.StatusBadRequest, errors.New("user does not exist") - } - - return p.Response.OK(w, "user deleted") -} - -// DestroyAll . -// swagger:route DELETE /v1/user user UserDestroyAll -// -// Delete all users. -// -// Security: -// token: -// -// Responses: -// 200: OKResponse -// 400: BadRequestResponse -// 401: UnauthorizedResponse -// 500: InternalServerErrorResponse -func (p *Endpoint) DestroyAll(w http.ResponseWriter, r *http.Request) (int, error) { - // Delete all items. - u := NewUser(p.DB, p.Q) - count, err := u.DeleteAll(u) - if err != nil { - return http.StatusInternalServerError, err - } else if count < 1 { - return http.StatusBadRequest, errors.New("no users to delete") - } - - return p.Response.OK(w, "users deleted") -} diff --git a/src/app/webapi/component/user/endpoint_create_test.go b/src/app/webapi/component/user/endpoint_create_test.go deleted file mode 100644 index bfb17c7..0000000 --- a/src/app/webapi/component/user/endpoint_create_test.go +++ /dev/null @@ -1,165 +0,0 @@ -package user_test - -import ( - "errors" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "testing" - - "app/webapi/component" - "app/webapi/component/user" - "app/webapi/pkg/router" - - "github.com/stretchr/testify/assert" -) - -func TestCreate(t *testing.T) { - core, m := component.NewCoreMock() - - mux := router.New() - user.New(core).Routes(mux) - - m.DB.SetRecordExistsString(func() (exists bool, ID string, err error) { - return false, "", nil - }) - - m.DB.SetAddRecordString(func() (ID string, err error) { - return "123", nil - }) - - form := url.Values{} - form.Add("first_name", "John") - form.Add("last_name", "Smith") - form.Add("email", "jsmith@example.com") - form.Add("password", "password") - - r := httptest.NewRequest("POST", "/v1/user", strings.NewReader(form.Encode())) - r.Header.Add("Content-Type", "application/x-www-form-urlencoded") - w := httptest.NewRecorder() - mux.ServeHTTP(w, r) - - assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), `{"status":"Created","record_id":"123"}`) -} - -func TestCreateUserAlreadyExists(t *testing.T) { - core, m := component.NewCoreMock() - - mux := router.New() - user.New(core).Routes(mux) - - m.DB.SetRecordExistsString(func() (exists bool, ID string, err error) { - return true, "", nil - }) - - form := url.Values{} - form.Add("first_name", "John") - form.Add("last_name", "Smith") - form.Add("email", "jsmith@example.com") - form.Add("password", "password") - - r := httptest.NewRequest("POST", "/v1/user", strings.NewReader(form.Encode())) - r.Header.Add("Content-Type", "application/x-www-form-urlencoded") - w := httptest.NewRecorder() - mux.ServeHTTP(w, r) - - assert.Equal(t, http.StatusBadRequest, w.Code) - assert.Contains(t, w.Body.String(), `{"status":"Bad Request","message":"user already exists"}`) -} - -func TestCreateUserInternalError(t *testing.T) { - core, m := component.NewCoreMock() - - mux := router.New() - user.New(core).Routes(mux) - - m.DB.SetRecordExistsString(func() (exists bool, ID string, err error) { - return true, "", errors.New("bad error") - }) - - form := url.Values{} - form.Add("first_name", "John") - form.Add("last_name", "Smith") - form.Add("email", "jsmith@example.com") - form.Add("password", "password") - - r := httptest.NewRequest("POST", "/v1/user", strings.NewReader(form.Encode())) - r.Header.Add("Content-Type", "application/x-www-form-urlencoded") - w := httptest.NewRecorder() - mux.ServeHTTP(w, r) - - assert.Equal(t, http.StatusInternalServerError, w.Code) - assert.Contains(t, w.Body.String(), `{"status":"Internal Server Error","message":"bad error"}`) -} - -func TestCreateUserInternalError2(t *testing.T) { - core, m := component.NewCoreMock() - - mux := router.New() - user.New(core).Routes(mux) - - m.DB.SetRecordExistsString(func() (exists bool, ID string, err error) { - return false, "", nil - }) - - m.DB.SetAddRecordString(func() (ID string, err error) { - return "", errors.New("bad error 2") - }) - - form := url.Values{} - form.Add("first_name", "John") - form.Add("last_name", "Smith") - form.Add("email", "jsmith@example.com") - form.Add("password", "password") - - r := httptest.NewRequest("POST", "/v1/user", strings.NewReader(form.Encode())) - r.Header.Add("Content-Type", "application/x-www-form-urlencoded") - w := httptest.NewRecorder() - mux.ServeHTTP(w, r) - - assert.Equal(t, http.StatusInternalServerError, w.Code) - assert.Contains(t, w.Body.String(), `{"status":"Internal Server Error","message":"bad error 2"}`) -} - -func TestCreateMissingItem(t *testing.T) { - core, _ := component.NewCoreMock() - - mux := router.New() - user.New(core).Routes(mux) - - form := url.Values{} - form.Add("first_name", "John") - form.Add("last_name", "Smith") - form.Add("email", "jsmith@example.com") - - r := httptest.NewRequest("POST", "/v1/user", strings.NewReader(form.Encode())) - r.Header.Add("Content-Type", "application/x-www-form-urlencoded") - w := httptest.NewRecorder() - mux.ServeHTTP(w, r) - - assert.Equal(t, http.StatusBadRequest, w.Code) - assert.Contains(t, w.Body.String(), `Error:Field validation for 'Password' failed on the 'required' tag`) -} - -func TestCreateBadEmail(t *testing.T) { - core, _ := component.NewCoreMock() - - mux := router.New() - user.New(core).Routes(mux) - - form := url.Values{} - form.Add("first_name", "John") - form.Add("last_name", "Smith") - form.Add("email", "jsmith@bademail") - form.Add("password", "password") - - r := httptest.NewRequest("POST", "/v1/user", strings.NewReader(form.Encode())) - r.Header.Add("Content-Type", "application/x-www-form-urlencoded") - w := httptest.NewRecorder() - mux.ServeHTTP(w, r) - - assert.Equal(t, http.StatusBadRequest, w.Code) - assert.Contains(t, w.Body.String(), `Error:Field validation for 'Email' failed on the 'email' tag`) -} diff --git a/src/app/webapi/component/user/index.go b/src/app/webapi/component/user/index.go new file mode 100644 index 0000000..e43cbef --- /dev/null +++ b/src/app/webapi/component/user/index.go @@ -0,0 +1,47 @@ +package user + +import ( + "net/http" + + "app/webapi/store" +) + +// Index . +// swagger:route GET /v1/user user UserIndex +// +// Return all users. +// +// Security: +// token: +// +// Responses: +// 200: UserIndexResponse +// 400: BadRequestResponse +// 401: UnauthorizedResponse +// 500: InternalServerErrorResponse +func (p *Endpoint) Index(w http.ResponseWriter, r *http.Request) (int, error) { + // Create the store. + u := store.NewUser(p.DB, p.Q) + + // Get all items. + results := make(store.TUserGroup, 0) + _, err := u.FindAll(&results) + if err != nil { + return http.StatusInternalServerError, err + } + + // Response returns 200. + // swagger:response UserIndexResponse + type response struct { + // in: body + Body struct { + // Required: true + Status string `json:"status"` + // Required: true + Data store.TUserGroup `json:"data"` + } + } + + resp := new(response) + return p.Response.Results(w, &resp.Body, results) +} diff --git a/src/app/webapi/component/user/endpoint_index_test.go b/src/app/webapi/component/user/index_test.go similarity index 67% rename from src/app/webapi/component/user/endpoint_index_test.go rename to src/app/webapi/component/user/index_test.go index 3fe302c..4e823e8 100644 --- a/src/app/webapi/component/user/endpoint_index_test.go +++ b/src/app/webapi/component/user/index_test.go @@ -7,20 +7,20 @@ import ( "app/webapi/component" "app/webapi/component/user" - "app/webapi/internal/testutil" + "app/webapi/internal/testdb" "app/webapi/pkg/router" + "app/webapi/store" "github.com/stretchr/testify/assert" ) func TestIndexEmpty(t *testing.T) { - core, m := component.NewCoreMock() + testdb.SetupTest(t) + core, _ := component.NewCoreMock() mux := router.New() user.New(core).Routes(mux) - m.DB.SetPaginatedResults(testutil.PaginatedResultsEmpty) - r := httptest.NewRequest("GET", "/v1/user", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, r) @@ -30,19 +30,15 @@ func TestIndexEmpty(t *testing.T) { } func TestIndexOne(t *testing.T) { - core, m := component.NewCoreMock() + testdb.SetupTest(t) + core, _ := component.NewCoreMock() mux := router.New() user.New(core).Routes(mux) - m.DB.SetPaginatedResults(func() (interface{}, int, error) { - results := make([]user.TUser, 0) - results = append(results, user.TUser{ - FirstName: "John", - LastName: "Smith", - }) - return results, 1, nil - }) + u := store.NewUser(core.DB, core.Q) + _, err := u.Create("John", "Smith", "jsmith@example.com", "password") + assert.Nil(t, err) r := httptest.NewRequest("GET", "/v1/user", nil) w := httptest.NewRecorder() diff --git a/src/app/webapi/component/user/request_test.go b/src/app/webapi/component/user/request_test.go new file mode 100644 index 0000000..93e528e --- /dev/null +++ b/src/app/webapi/component/user/request_test.go @@ -0,0 +1,37 @@ +package user_test + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "app/webapi/component" + "app/webapi/component/user" + "app/webapi/internal/testdb" + "app/webapi/pkg/router" + + "github.com/stretchr/testify/assert" +) + +func TestRequestValidation(t *testing.T) { + for _, v := range []string{ + "POST /v1/user", + "DELETE /v1/user/1", + } { + arr := strings.Split(v, " ") + + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + r := httptest.NewRequest(arr[0], arr[1], nil) + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusBadRequest, w.Code) + } +} diff --git a/src/app/webapi/component/user/show.go b/src/app/webapi/component/user/show.go new file mode 100644 index 0000000..1b2996b --- /dev/null +++ b/src/app/webapi/component/user/show.go @@ -0,0 +1,67 @@ +package user + +import ( + "errors" + "net/http" + + "app/webapi/store" +) + +// ***************************************************************************** +// Read +// ***************************************************************************** + +// Show . +// swagger:route GET /v1/user/{user_id} user UserShow +// +// Return one user. +// +// Security: +// token: +// +// Responses: +// 200: UserShowResponse +// 400: BadRequestResponse +// 401: UnauthorizedResponse +// 500: InternalServerErrorResponse +func (p *Endpoint) Show(w http.ResponseWriter, r *http.Request) (int, error) { + // swagger:parameters UserShow + type request struct { + // in: path + UserID string `json:"user_id" validate:"required"` + } + + // Request validation. + req := new(request) + if err := p.Bind.FormUnmarshal(req, r); err != nil { + return http.StatusBadRequest, err + } else if err = p.Bind.Validate(req); err != nil { + return http.StatusBadRequest, err + } + + // Create the store. + u := store.NewUser(p.DB, p.Q) + + // Get a user. + exists, err := u.FindOneByID(u, req.UserID) + if err != nil { + return http.StatusInternalServerError, err + } else if !exists { + return http.StatusBadRequest, errors.New("item not found") + } + + // Response returns 200. + // swagger:response UserShowResponse + type response struct { + // in: body + Body struct { + // Required: true + Status string `json:"status"` + // Required: true + Data []store.TUser `json:"data"` + } + } + + resp := new(response) + return p.Response.Results(w, &resp.Body, []store.TUser{*u}) +} diff --git a/src/app/webapi/component/user/show_test.go b/src/app/webapi/component/user/show_test.go new file mode 100644 index 0000000..6bbeda0 --- /dev/null +++ b/src/app/webapi/component/user/show_test.go @@ -0,0 +1,49 @@ +package user_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "app/webapi/component" + "app/webapi/component/user" + "app/webapi/internal/testdb" + "app/webapi/pkg/router" + "app/webapi/store" + + "github.com/stretchr/testify/assert" +) + +func TestShowOne(t *testing.T) { + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + u := store.NewUser(core.DB, core.Q) + ID, err := u.Create("John", "Smith", "jsmith@example.com", "password") + assert.Nil(t, err) + + r := httptest.NewRequest("GET", "/v1/user/"+ID, nil) + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), `"first_name":"John","last_name":"Smith","email":"jsmith@example.com"`) +} + +func TestShowNotFound(t *testing.T) { + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + r := httptest.NewRequest("GET", "/v1/user/1", nil) + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, w.Body.String(), `{"status":"Bad Request","message":"item not found"}`) +} diff --git a/src/app/webapi/component/user/update.go b/src/app/webapi/component/user/update.go new file mode 100644 index 0000000..86ae8b0 --- /dev/null +++ b/src/app/webapi/component/user/update.go @@ -0,0 +1,68 @@ +package user + +import ( + "errors" + "net/http" + + "app/webapi/store" +) + +// Update . +// swagger:route PUT /v1/user/{user_id} user UserUpdate +// +// Make changes to a user. +// +// Security: +// token: +// +// Responses: +// 200: OKResponse +// 400: BadRequestResponse +// 401: UnauthorizedResponse +// 500: InternalServerErrorResponse +func (p *Endpoint) Update(w http.ResponseWriter, r *http.Request) (int, error) { + // swagger:parameters UserUpdate + type request struct { + // in: path + UserID string `json:"user_id" validate:"required"` + // in: formData + // Required: true + FirstName string `json:"first_name" validate:"required"` + // in: formData + // Required: true + LastName string `json:"last_name" validate:"required"` + // in: formData + // Required: true + Email string `json:"email" validate:"required"` + // in: formData + // Required: true + Password string `json:"password" validate:"required"` + } + + // Request validation. + req := new(request) + if err := p.Bind.FormUnmarshal(req, r); err != nil { + return http.StatusBadRequest, err + } else if err = p.Bind.Validate(req); err != nil { + return http.StatusBadRequest, err + } + + // Create the store. + u := store.NewUser(p.DB, p.Q) + + // Determine if the user exists. + exists, err := u.ExistsByID(u, req.UserID) + if err != nil { + return http.StatusInternalServerError, err + } else if !exists { + return http.StatusBadRequest, errors.New("user not found") + } + + // Update item. + err = u.Update(u.ID, req.FirstName, req.LastName, req.Email, req.Password) + if err != nil { + return http.StatusInternalServerError, err + } + + return p.Response.OK(w, "user updated") +} diff --git a/src/app/webapi/component/user/update_test.go b/src/app/webapi/component/user/update_test.go new file mode 100644 index 0000000..dc3b2a5 --- /dev/null +++ b/src/app/webapi/component/user/update_test.go @@ -0,0 +1,82 @@ +package user_test + +import ( + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "app/webapi/component" + "app/webapi/component/user" + "app/webapi/internal/testdb" + "app/webapi/pkg/router" + "app/webapi/store" + + "github.com/stretchr/testify/assert" +) + +func TestUpdateUserAllFields(t *testing.T) { + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + u := store.NewUser(core.DB, core.Q) + ID, err := u.Create("John", "Smith", "jsmith@example.com", "password") + assert.Nil(t, err) + + form := url.Values{} + form.Add("first_name", "John1") + form.Add("last_name", "Smith2") + form.Add("email", "jsmith3@example.com") + form.Add("password", "password4") + + r := httptest.NewRequest("PUT", "/v1/user/"+ID, strings.NewReader(form.Encode())) + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), `{"status":"OK","message":"user updated"}`) + + found, err := u.FindOneByID(u, ID) + assert.Nil(t, err) + assert.True(t, found) + assert.Equal(t, "John1", u.FirstName) + assert.Equal(t, "Smith2", u.LastName) + assert.Equal(t, "jsmith3@example.com", u.Email) + assert.Equal(t, "password4", u.Password) +} + +func TestUpdateMissingFields(t *testing.T) { + testdb.SetupTest(t) + core, _ := component.NewCoreMock() + + mux := router.New() + user.New(core).Routes(mux) + + u := store.NewUser(core.DB, core.Q) + ID, err := u.Create("John", "Smith", "jsmith@example.com", "password") + assert.Nil(t, err) + + form := url.Values{} + form.Add("first_name", "John1") + + r := httptest.NewRequest("PUT", "/v1/user/"+ID, strings.NewReader(form.Encode())) + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + w := httptest.NewRecorder() + mux.ServeHTTP(w, r) + + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, w.Body.String(), `Error:Field validation`) + + found, err := u.FindOneByID(u, ID) + assert.Nil(t, err) + assert.True(t, found) + assert.Equal(t, "John", u.FirstName) + assert.Equal(t, "Smith", u.LastName) + assert.Equal(t, "jsmith@example.com", u.Email) + assert.Equal(t, "password", u.Password) +} diff --git a/src/app/webapi/internal/testdb/testdb.go b/src/app/webapi/internal/testdb/testdb.go new file mode 100644 index 0000000..c9e2937 --- /dev/null +++ b/src/app/webapi/internal/testdb/testdb.go @@ -0,0 +1,40 @@ +package testdb + +import ( + "app/webapi/component" + "io/ioutil" + "log" + "os" + "strings" + "testing" +) + +// SetupTest will set up the DB for the testss. +func SetupTest(t *testing.T) { + db := component.TestDatabase(false) + db.Exec(`DROP DATABASE IF EXISTS webapitest`) + db.Exec(`CREATE DATABASE webapitest DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci`) + + db = component.TestDatabase(true) + b, err := ioutil.ReadFile("../../../../../migration/tables-only.sql") + if err != nil { + log.Println(err) + os.Exit(1) + } + + // Split each statement. + stmts := strings.Split(string(b), ";") + for i, s := range stmts { + if i == len(stmts)-1 { + break + } + _, err = db.Exec(s) + if err != nil { + log.Println(err) + } + } + + //exit := m.Run() + //db.Exec(`DROP DATABASE IF EXISTS webapitest`) + //os.Exit(exit) +} diff --git a/src/app/webapi/component/user/user.go b/src/app/webapi/store/user.go similarity index 66% rename from src/app/webapi/component/user/user.go rename to src/app/webapi/store/user.go index 34fb500..f29fbe5 100644 --- a/src/app/webapi/component/user/user.go +++ b/src/app/webapi/store/user.go @@ -1,4 +1,4 @@ -package user +package store import ( "time" @@ -17,8 +17,8 @@ func NewUser(db component.IDatabase, q component.IQuery) *TUser { // TUser represents a user. type TUser struct { - component.IQuery - db component.IDatabase + component.IQuery `json:"-"` + db component.IDatabase ID string `db:"id" json:"id"` FirstName string `db:"first_name" json:"first_name"` @@ -60,18 +60,6 @@ func (x TUserGroup) PrimaryKey() string { return "id" } -//TODO: This should be used as an example. -// FindOneByID will find the user by string ID. -/*func (x *TUser) FindOneByID(dest component.IRecord, ID string) (exists bool, err error) { - ID = "2" - err = x.db.Get(dest, fmt.Sprintf(` - SELECT * FROM %s - WHERE %s = ? - LIMIT 1`, x.Table(), x.PrimaryKey()), - ID) - return (err != sql.ErrNoRows), x.db.Error(err) -}*/ - // ***************************************************************************** // Create // ***************************************************************************** @@ -96,36 +84,6 @@ func (x *TUser) Create(firstName, lastName, email, password string) (string, err return uuid, err } -// ***************************************************************************** -// Read -// ***************************************************************************** - -// One returns one user with the matching ID. -/*func One(db component.IDatabase, ID string) (p TUser, exists bool, err error) { - err = db.Get(&p, ` - SELECT * FROM user - WHERE id = ? - LIMIT 1`, - ID) - return p, (err != sql.ErrNoRows), db.Error(err) -}*/ - -// All returns all users. -/*func All(db component.IDatabase) (result []TUser, total int, err error) { - result = make([]TUser, 0) - - err = db.Get(&total, ` - SELECT COUNT(DISTINCT id) - FROM user - WHERE deleted_at IS NULL;`) - if err != nil { - return result, total, db.Error(err) - } - - err = db.Select(&result, `SELECT * FROM user`) - return result, total, err -}*/ - // ***************************************************************************** // Update // *****************************************************************************