From acf058b0062324a3efcb568d14debf2ac9938d62 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 1 Aug 2020 03:46:51 +0300 Subject: [PATCH] add database/orm/reform example --- HISTORY.md | 2 + _examples/README.md | 1 + .../reform/controllers/person_controller.go | 47 ++++++ _examples/database/orm/reform/go.mod | 11 ++ _examples/database/orm/reform/main.go | 50 +++++++ .../database/orm/reform/models/person.go | 13 ++ .../orm/reform/models/person_reform.go | 136 ++++++++++++++++++ .../orm/reform/postman_collection.json | 58 ++++++++ hero/container.go | 11 ++ hero/reflect.go | 12 +- 10 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 _examples/database/orm/reform/controllers/person_controller.go create mode 100644 _examples/database/orm/reform/go.mod create mode 100644 _examples/database/orm/reform/main.go create mode 100644 _examples/database/orm/reform/models/person.go create mode 100644 _examples/database/orm/reform/models/person_reform.go create mode 100644 _examples/database/orm/reform/postman_collection.json diff --git a/HISTORY.md b/HISTORY.md index 3c1f9671..1ffbae49 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -153,6 +153,8 @@ Prior to this version the `iris.Context` was the only one dependency that has be | [http.ResponseWriter](https://golang.org/pkg/net/http/#ResponseWriter) | `ctx.ResponseWriter()` | | [http.Header](https://golang.org/pkg/net/http/#Header) | `ctx.Request().Header` | | [time.Time](https://golang.org/pkg/time/#Time) | `time.Now()` | +| [golog.Logger](https://pkg.go.dev/github.com/kataras/golog) | Iris Logger | +| [net.IP](https://golang.org/pkg/net/#IP) | `net.ParseIP(ctx.RemoteAddr())` | | `string`, | | | `int, int8, int16, int32, int64`, | | | `uint, uint8, uint16, uint32, uint64`, | | diff --git a/_examples/README.md b/_examples/README.md index 0f15eda6..bcc154a1 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -9,6 +9,7 @@ * [MongoDB](database/mongodb) * [Xorm](database/orm/xorm/main.go) * [Gorm](database/orm/gorm/main.go) + * [Reform](database/orm/reform/main.go) * HTTP Server * [HOST:PORT](http-server/listen-addr/main.go) * [Public Test Domain](http-server/listen-addr-public/main.go) diff --git a/_examples/database/orm/reform/controllers/person_controller.go b/_examples/database/orm/reform/controllers/person_controller.go new file mode 100644 index 00000000..97f9dbb6 --- /dev/null +++ b/_examples/database/orm/reform/controllers/person_controller.go @@ -0,0 +1,47 @@ +package controllers + +import ( + "net" + "time" + + "myapp/models" + + "github.com/kataras/golog" + "gopkg.in/reform.v1" +) + +// PersonController is the model.Person's web controller. +type PersonController struct { + DB *reform.DB + + // Logger and IP fields are automatically binded by the framework. + Logger *golog.Logger // binds to the application's logger. + IP net.IP // binds to the client's IP. +} + +// Get handles +// GET /persons +func (c *PersonController) Get() ([]reform.Struct, error) { + return c.DB.SelectAllFrom(models.PersonTable, "") +} + +// GetBy handles +// GET /persons/{ID} +func (c *PersonController) GetBy(id int32) (reform.Record, error) { + return c.DB.FindByPrimaryKeyFrom(models.PersonTable, id) +} + +// Post handles +// POST /persons with JSON request body of model.Person. +func (c *PersonController) Post(p *models.Person) int { + p.CreatedAt = time.Now() + + if err := c.DB.Save(p); err != nil { + c.Logger.Errorf("[%s] create person: %v", c.IP.String(), err) + return 500 // iris.StatusInternalServerError + } + + c.Logger.Debugf("[%s] create person [%s] succeed", c.IP.String(), p.Name) + + return 201 // iris.StatusCreated +} diff --git a/_examples/database/orm/reform/go.mod b/_examples/database/orm/reform/go.mod new file mode 100644 index 00000000..06eefd9a --- /dev/null +++ b/_examples/database/orm/reform/go.mod @@ -0,0 +1,11 @@ +module myapp + +go 1.14 + +require ( + github.com/kataras/iris/v12 v12.1.8 + github.com/mattn/go-sqlite3 v1.14.0 + gopkg.in/reform.v1 v1.4.0 +) + +replace github.com/kataras/iris/v12 => ../../../../ diff --git a/_examples/database/orm/reform/main.go b/_examples/database/orm/reform/main.go new file mode 100644 index 00000000..9be385b4 --- /dev/null +++ b/_examples/database/orm/reform/main.go @@ -0,0 +1,50 @@ +package main + +/* +$ go get gopkg.in/reform.v1/reform +$ go generate ./models +$ go run . + +Read more at: https://github.com/go-reform/reform +*/ + +import ( + "database/sql" + + "myapp/controllers" + + "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/mvc" + + _ "github.com/mattn/go-sqlite3" + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/dialects/sqlite3" +) + +func main() { + app := iris.New() + app.Logger().SetLevel("debug") + + sqlDB, err := sql.Open("sqlite3", "./myapp.db") + if err != nil { + panic(err) + } + defer sqlDB.Close() + sqlStmt := ` + drop table people; + create table people (id integer not null primary key, name text, email text, created_at datetime not null, updated_at datetime null); + delete from people; + ` + _, err = sqlDB.Exec(sqlStmt) + if err != nil { + panic(err) + } + + db := reform.NewDB(sqlDB, sqlite3.Dialect, reform.NewPrintfLogger(app.Logger().Debugf)) + + mvcApp := mvc.New(app.Party("/persons")) + mvcApp.Register(db) + mvcApp.Handle(new(controllers.PersonController)) + + app.Listen(":8080") +} diff --git a/_examples/database/orm/reform/models/person.go b/_examples/database/orm/reform/models/person.go new file mode 100644 index 00000000..6b852748 --- /dev/null +++ b/_examples/database/orm/reform/models/person.go @@ -0,0 +1,13 @@ +//go:generate reform +package models + +import "time" + +//reform:people +type Person struct { + ID int32 `reform:"id,pk" json:"id"` + Name string `reform:"name" json:"name"` + Email *string `reform:"email" json:"email"` + CreatedAt time.Time `reform:"created_at" json:"created_at"` + UpdatedAt *time.Time `reform:"updated_at" json:"updated_at"` +} diff --git a/_examples/database/orm/reform/models/person_reform.go b/_examples/database/orm/reform/models/person_reform.go new file mode 100644 index 00000000..537d2b5d --- /dev/null +++ b/_examples/database/orm/reform/models/person_reform.go @@ -0,0 +1,136 @@ +// Code generated by gopkg.in/reform.v1. DO NOT EDIT. + +package models + +import ( + "fmt" + "strings" + + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/parse" +) + +type personTableType struct { + s parse.StructInfo + z []interface{} +} + +// Schema returns a schema name in SQL database (""). +func (v *personTableType) Schema() string { + return v.s.SQLSchema +} + +// Name returns a view or table name in SQL database ("people"). +func (v *personTableType) Name() string { + return v.s.SQLName +} + +// Columns returns a new slice of column names for that view or table in SQL database. +func (v *personTableType) Columns() []string { + return []string{"id", "name", "email", "created_at", "updated_at"} +} + +// NewStruct makes a new struct for that view or table. +func (v *personTableType) NewStruct() reform.Struct { + return new(Person) +} + +// NewRecord makes a new record for that table. +func (v *personTableType) NewRecord() reform.Record { + return new(Person) +} + +// PKColumnIndex returns an index of primary key column for that table in SQL database. +func (v *personTableType) PKColumnIndex() uint { + return uint(v.s.PKFieldIndex) +} + +// PersonTable represents people view or table in SQL database. +var PersonTable = &personTableType{ + s: parse.StructInfo{Type: "Person", SQLSchema: "", SQLName: "people", Fields: []parse.FieldInfo{{Name: "ID", Type: "int32", Column: "id"}, {Name: "Name", Type: "string", Column: "name"}, {Name: "Email", Type: "*string", Column: "email"}, {Name: "CreatedAt", Type: "time.Time", Column: "created_at"}, {Name: "UpdatedAt", Type: "*time.Time", Column: "updated_at"}}, PKFieldIndex: 0}, + z: new(Person).Values(), +} + +// String returns a string representation of this struct or record. +func (s Person) String() string { + res := make([]string, 5) + res[0] = "ID: " + reform.Inspect(s.ID, true) + res[1] = "Name: " + reform.Inspect(s.Name, true) + res[2] = "Email: " + reform.Inspect(s.Email, true) + res[3] = "CreatedAt: " + reform.Inspect(s.CreatedAt, true) + res[4] = "UpdatedAt: " + reform.Inspect(s.UpdatedAt, true) + return strings.Join(res, ", ") +} + +// Values returns a slice of struct or record field values. +// Returned interface{} values are never untyped nils. +func (s *Person) Values() []interface{} { + return []interface{}{ + s.ID, + s.Name, + s.Email, + s.CreatedAt, + s.UpdatedAt, + } +} + +// Pointers returns a slice of pointers to struct or record fields. +// Returned interface{} values are never untyped nils. +func (s *Person) Pointers() []interface{} { + return []interface{}{ + &s.ID, + &s.Name, + &s.Email, + &s.CreatedAt, + &s.UpdatedAt, + } +} + +// View returns View object for that struct. +func (s *Person) View() reform.View { + return PersonTable +} + +// Table returns Table object for that record. +func (s *Person) Table() reform.Table { + return PersonTable +} + +// PKValue returns a value of primary key for that record. +// Returned interface{} value is never untyped nil. +func (s *Person) PKValue() interface{} { + return s.ID +} + +// PKPointer returns a pointer to primary key field for that record. +// Returned interface{} value is never untyped nil. +func (s *Person) PKPointer() interface{} { + return &s.ID +} + +// HasPK returns true if record has non-zero primary key set, false otherwise. +func (s *Person) HasPK() bool { + return s.ID != PersonTable.z[PersonTable.s.PKFieldIndex] +} + +// SetPK sets record primary key. +func (s *Person) SetPK(pk interface{}) { + if i64, ok := pk.(int64); ok { + s.ID = int32(i64) + } else { + s.ID = pk.(int32) + } +} + +// check interfaces +var ( + _ reform.View = PersonTable + _ reform.Struct = (*Person)(nil) + _ reform.Table = PersonTable + _ reform.Record = (*Person)(nil) + _ fmt.Stringer = (*Person)(nil) +) + +func init() { + parse.AssertUpToDate(&PersonTable.s, new(Person)) +} diff --git a/_examples/database/orm/reform/postman_collection.json b/_examples/database/orm/reform/postman_collection.json new file mode 100644 index 00000000..b72a9bd5 --- /dev/null +++ b/_examples/database/orm/reform/postman_collection.json @@ -0,0 +1,58 @@ +{ + "info": { + "_postman_id": "6b66000d-9c04-4d0a-b55c-8a493bf59015", + "name": "iris-reform-example", + "description": "Example API calls for iris reform example.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "http://localhost:8080/persons", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"John\",\r\n \"email\": \"example@example.com\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/persons", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "persons" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:8080/persons", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/persons", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "persons" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} +} \ No newline at end of file diff --git a/hero/container.go b/hero/container.go index 6def3fbb..b82cebcd 100644 --- a/hero/container.go +++ b/hero/container.go @@ -3,12 +3,15 @@ package hero import ( stdContext "context" "errors" + "net" "net/http" "reflect" "time" "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/sessions" + + "github.com/kataras/golog" ) // Default is the default container value which can be used for dependencies share. @@ -57,6 +60,10 @@ var BuiltinDependencies = []*Dependency{ return session }).Explicitly(), + // application's logger. + NewDependency(func(ctx *context.Context) *golog.Logger { + return ctx.Application().Logger() + }).Explicitly(), // time.Time to time.Now dependency. NewDependency(func(ctx *context.Context) time.Time { return time.Now() @@ -73,6 +80,10 @@ var BuiltinDependencies = []*Dependency{ NewDependency(func(ctx *context.Context) http.Header { return ctx.Request().Header }).Explicitly(), + // Client IP. + NewDependency(func(ctx *context.Context) net.IP { + return net.ParseIP(ctx.RemoteAddr()) + }).Explicitly(), // payload and param bindings are dynamically allocated and declared at the end of the `binding` source file. } diff --git a/hero/reflect.go b/hero/reflect.go index 3db0a99d..f7c8b186 100644 --- a/hero/reflect.go +++ b/hero/reflect.go @@ -1,6 +1,7 @@ package hero import ( + "net" "reflect" "github.com/kataras/iris/v12/context" @@ -41,9 +42,12 @@ func isFunc(kindable interface{ Kind() reflect.Kind }) bool { return kindable.Kind() == reflect.Func } -var inputTyp = reflect.TypeOf((*Input)(nil)) +var ( + inputTyp = reflect.TypeOf((*Input)(nil)) + errTyp = reflect.TypeOf((*error)(nil)).Elem() -var errTyp = reflect.TypeOf((*error)(nil)).Elem() + ipTyp = reflect.TypeOf(net.IP{}) +) // isError returns true if "typ" is type of `error`. func isError(typ reflect.Type) bool { @@ -221,6 +225,10 @@ func isZero(v reflect.Value) bool { return false } + if v.Type() == ipTyp { + return len(v.Interface().(net.IP)) == 0 + } + zero := reflect.Zero(v.Type()) return v.Interface() == zero.Interface() }