1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-18 02:17:05 +00:00

reorganization of _examples and add some new examples such as iris+groupcache+mysql+docker

Former-commit-id: ed635ee95de7160cde11eaabc0c1dcb0e460a620
This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-06-07 15:26:06 +03:00
parent 9fdcb4c7fb
commit ed45c77be5
328 changed files with 4262 additions and 41621 deletions

View File

@@ -0,0 +1,97 @@
// Package api contains the handlers for our HTTP Endpoints.
package api
import (
"time"
"myapp/service"
"myapp/sql"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
"github.com/kataras/iris/v12/middleware/requestid"
)
// Router accepts any required dependencies and returns the main server's handler.
func Router(db sql.Database, secret string) func(iris.Party) {
return func(r iris.Party) {
j := jwt.HMAC(15*time.Minute, secret)
r.Use(requestid.New())
r.Use(verifyToken(j))
// Generate a token for testing by navigating to
// http://localhost:8080/token endpoint.
// Copy-paste it to a ?token=$token url parameter or
// open postman and put an Authentication: Bearer $token to get
// access on create, update and delete endpoinds.
r.Get("/token", writeToken(j))
var (
categoryService = service.NewCategoryService(db)
productService = service.NewProductService(db)
)
cat := r.Party("/category")
{
// TODO: new Use to add middlewares to specific
// routes per METHOD ( we already have the per path through parties.)
handler := NewCategoryHandler(categoryService)
cat.Get("/", handler.List)
cat.Post("/", handler.Create)
cat.Put("/", handler.Update)
cat.Get("/{id:int64}", handler.GetByID)
cat.Patch("/{id:int64}", handler.PartialUpdate)
cat.Delete("/{id:int64}", handler.Delete)
/* You can also do something like that:
cat.PartyFunc("/{id:int64}", func(c iris.Party) {
c.Get("/", handler.GetByID)
c.Post("/", handler.PartialUpdate)
c.Delete("/", handler.Delete)
})
*/
cat.Get("/{id:int64}/products", handler.ListProducts)
cat.Post("/{id:int64}/products", handler.InsertProducts(productService))
}
prod := r.Party("/product")
{
handler := NewProductHandler(productService)
prod.Get("/", handler.List)
prod.Post("/", handler.Create)
prod.Put("/", handler.Update)
prod.Get("/{id:int64}", handler.GetByID)
prod.Patch("/{id:int64}", handler.PartialUpdate)
prod.Delete("/{id:int64}", handler.Delete)
}
}
}
func writeToken(j *jwt.JWT) iris.Handler {
return func(ctx iris.Context) {
claims := jwt.Claims{
Issuer: "https://iris-go.com",
Audience: jwt.Audience{requestid.Get(ctx)},
}
j.WriteToken(ctx, claims)
}
}
func verifyToken(j *jwt.JWT) iris.Handler {
return func(ctx iris.Context) {
// Allow all GET.
if ctx.Method() == iris.MethodGet {
ctx.Next()
return
}
j.Verify(ctx)
}
}

View File

@@ -0,0 +1,251 @@
package api
import (
"myapp/entity"
"myapp/service"
"myapp/sql"
"github.com/kataras/iris/v12"
)
// CategoryHandler is the http mux for categories.
type CategoryHandler struct {
// [...options]
service *service.CategoryService
}
// NewCategoryHandler returns the main controller for the categories API.
func NewCategoryHandler(service *service.CategoryService) *CategoryHandler {
return &CategoryHandler{service}
}
// GetByID fetches a single record from the database and sends it to the client.
// Method: GET.
func (h *CategoryHandler) GetByID(ctx iris.Context) {
id := ctx.Params().GetInt64Default("id", 0)
var cat entity.Category
err := h.service.GetByID(ctx.Request().Context(), &cat, id)
if err != nil {
if err == sql.ErrNoRows {
writeEntityNotFound(ctx)
return
}
debugf("CategoryHandler.GetByID(id=%d): %v", id, err)
writeInternalServerError(ctx)
return
}
ctx.JSON(cat)
}
/*
type (
List struct {
Data interface{} `json:"data"`
Order string `json:"order"`
Next Range `json:"next,omitempty"`
Prev Range `json:"prev,omitempty"`
}
Range struct {
Offset int64 `json:"offset"`
Limit int64 `json:"limit`
}
)
*/
// List lists a set of records from the database.
// Method: GET.
func (h *CategoryHandler) List(ctx iris.Context) {
q := ctx.Request().URL.Query()
opts := sql.ParseListOptions(q)
// initialize here in order to return an empty json array `[]` instead of `null`.
categories := entity.Categories{}
err := h.service.List(ctx.Request().Context(), &categories, opts)
if err != nil && err != sql.ErrNoRows {
debugf("CategoryHandler.List(DB) (limit=%d offset=%d where=%s=%v): %v",
opts.Limit, opts.Offset, opts.WhereColumn, opts.WhereValue, err)
writeInternalServerError(ctx)
return
}
ctx.JSON(categories)
}
// Create adds a record to the database.
// Method: POST.
func (h *CategoryHandler) Create(ctx iris.Context) {
var cat entity.Category
if err := ctx.ReadJSON(&cat); err != nil {
return
}
id, err := h.service.Insert(ctx.Request().Context(), cat)
if err != nil {
if err == sql.ErrUnprocessable {
ctx.StopWithJSON(iris.StatusUnprocessableEntity, newError(iris.StatusUnprocessableEntity, ctx.Request().Method, ctx.Path(), "required fields are missing"))
return
}
debugf("CategoryHandler.Create(DB): %v", err)
writeInternalServerError(ctx)
return
}
// Send 201 with body of {"id":$last_inserted_id"}.
ctx.StatusCode(iris.StatusCreated)
ctx.JSON(iris.Map{cat.PrimaryKey(): id})
}
// Update performs a full-update of a record in the database.
// Method: PUT.
func (h *CategoryHandler) Update(ctx iris.Context) {
var cat entity.Category
if err := ctx.ReadJSON(&cat); err != nil {
return
}
affected, err := h.service.Update(ctx.Request().Context(), cat)
if err != nil {
if err == sql.ErrUnprocessable {
ctx.StopWithJSON(iris.StatusUnprocessableEntity, newError(iris.StatusUnprocessableEntity, ctx.Request().Method, ctx.Path(), "required fields are missing"))
return
}
debugf("CategoryHandler.Update(DB): %v", err)
writeInternalServerError(ctx)
return
}
status := iris.StatusOK
if affected == 0 {
status = iris.StatusNotModified
}
ctx.StatusCode(status)
}
// PartialUpdate is the handler for partially update one or more fields of the record.
// Method: PATCH.
func (h *CategoryHandler) PartialUpdate(ctx iris.Context) {
id := ctx.Params().GetInt64Default("id", 0)
var attrs map[string]interface{}
if err := ctx.ReadJSON(&attrs); err != nil {
return
}
affected, err := h.service.PartialUpdate(ctx.Request().Context(), id, attrs)
if err != nil {
if err == sql.ErrUnprocessable {
ctx.StopWithJSON(iris.StatusUnprocessableEntity, newError(iris.StatusUnprocessableEntity, ctx.Request().Method, ctx.Path(), "unsupported value(s)"))
return
}
debugf("CategoryHandler.PartialUpdate(DB): %v", err)
writeInternalServerError(ctx)
return
}
status := iris.StatusOK
if affected == 0 {
status = iris.StatusNotModified
}
ctx.StatusCode(status)
}
// Delete removes a record from the database.
// Method: DELETE.
func (h *CategoryHandler) Delete(ctx iris.Context) {
id := ctx.Params().GetInt64Default("id", 0)
affected, err := h.service.DeleteByID(ctx.Request().Context(), id)
if err != nil {
debugf("CategoryHandler.Delete(DB): %v", err)
writeInternalServerError(ctx)
return
}
status := iris.StatusOK // StatusNoContent
if affected == 0 {
status = iris.StatusNotModified
}
ctx.StatusCode(status)
}
// Products.
// ListProducts lists products of a Category.
// Example: from cheap to expensive:
// http://localhost:8080/category/3/products?offset=0&limit=30&by=price&order=asc
// Method: GET.
func (h *CategoryHandler) ListProducts(ctx iris.Context) {
id := ctx.Params().GetInt64Default("id", 0)
// NOTE: could add cache here too.
q := ctx.Request().URL.Query()
opts := sql.ParseListOptions(q).Where("category_id", id)
opts.Table = "products"
if opts.OrderByColumn == "" {
opts.OrderByColumn = "updated_at"
}
var products entity.Products
err := h.service.List(ctx.Request().Context(), &products, opts)
if err != nil {
debugf("CategoryHandler.ListProducts(DB) (table=%s where=%s=%v limit=%d offset=%d): %v",
opts.Table, opts.WhereColumn, opts.WhereValue, opts.Limit, opts.Offset, err)
writeInternalServerError(ctx)
return
}
ctx.JSON(products)
}
// InsertProducts assigns new products to a Category (accepts a list of products).
// Method: POST.
func (h *CategoryHandler) InsertProducts(productService *service.ProductService) iris.Handler {
return func(ctx iris.Context) {
categoryID := ctx.Params().GetInt64Default("id", 0)
var products []entity.Product
if err := ctx.ReadJSON(&products); err != nil {
return
}
for i := range products {
products[i].CategoryID = categoryID
}
inserted, err := productService.BatchInsert(ctx.Request().Context(), products)
if err != nil {
if err == sql.ErrUnprocessable {
ctx.StopWithJSON(iris.StatusUnprocessableEntity, newError(iris.StatusUnprocessableEntity, ctx.Request().Method, ctx.Path(), "required fields are missing"))
return
}
debugf("CategoryHandler.InsertProducts(DB): %v", err)
writeInternalServerError(ctx)
return
}
if inserted == 0 {
ctx.StatusCode(iris.StatusNotModified)
return
}
// Send 201 with body of {"inserted":$inserted"}.
ctx.StatusCode(iris.StatusCreated)
ctx.JSON(iris.Map{"inserted": inserted})
}
}

View File

@@ -0,0 +1,25 @@
package api
import (
"log"
"github.com/kataras/iris/v12"
)
const debug = true
func debugf(format string, args ...interface{}) {
if !debug {
return
}
log.Printf(format, args...)
}
func writeInternalServerError(ctx iris.Context) {
ctx.StopWithJSON(iris.StatusInternalServerError, newError(iris.StatusInternalServerError, ctx.Request().Method, ctx.Path(), ""))
}
func writeEntityNotFound(ctx iris.Context) {
ctx.StopWithJSON(iris.StatusNotFound, newError(iris.StatusNotFound, ctx.Request().Method, ctx.Path(), "entity does not exist"))
}

View File

@@ -0,0 +1,60 @@
package api
import (
"fmt"
"time"
"github.com/kataras/iris/v12"
)
// Error holds the error sent by server to clients (JSON).
type Error struct {
StatusCode int `json:"code"`
Method string `json:"-"`
Path string `json:"-"`
Message string `json:"message"`
Timestamp int64 `json:"timestamp"`
}
func newError(statusCode int, method, path, format string, args ...interface{}) Error {
msg := format
if len(args) > 0 {
// why we check for that? If the original error message came from our database
// and it contains fmt-reserved words
// like %s or %d we will get MISSING(=...) in our error message and we don't want that.
msg = fmt.Sprintf(msg, args...)
}
if msg == "" {
msg = iris.StatusText(statusCode)
}
return Error{
StatusCode: statusCode,
Method: method,
Path: path,
Message: msg,
Timestamp: time.Now().Unix(),
}
}
// Error implements the internal Go error interface.
func (e Error) Error() string {
return fmt.Sprintf("[%d] %s: %s: %s", e.StatusCode, e.Method, e.Path, e.Message)
}
// Is implements the standard `errors.Is` internal interface.
// Usage: errors.Is(e, target)
func (e Error) Is(target error) bool {
if target == nil {
return false
}
err, ok := target.(Error)
if !ok {
return false
}
return (err.StatusCode == e.StatusCode || e.StatusCode == 0) &&
(err.Message == e.Message || e.Message == "")
}

View File

@@ -0,0 +1,173 @@
package api
import (
"time"
"myapp/cache"
"myapp/entity"
"myapp/service"
"myapp/sql"
"github.com/kataras/iris/v12"
)
// ProductHandler is the http mux for products.
type ProductHandler struct {
service *service.ProductService
cache *cache.Cache
}
// NewProductHandler returns the main controller for the products API.
func NewProductHandler(service *service.ProductService) *ProductHandler {
return &ProductHandler{
service: service,
cache: cache.New(service, "products", time.Minute),
}
}
// GetByID fetches a single record from the database and sends it to the client.
// Method: GET.
func (h *ProductHandler) GetByID(ctx iris.Context) {
id := ctx.Params().GetString("id")
var product []byte
err := h.cache.GetByID(ctx.Request().Context(), id, &product)
if err != nil {
if err == sql.ErrNoRows {
writeEntityNotFound(ctx)
return
}
debugf("ProductHandler.GetByID(id=%v): %v", id, err)
writeInternalServerError(ctx)
return
}
ctx.ContentType("application/json")
ctx.Write(product)
// ^ Could use our simple `noCache` or implement a Cache-Control (see kataras/iris/cache for that)
// but let's keep it simple.
}
// List lists a set of records from the database.
// Method: GET.
func (h *ProductHandler) List(ctx iris.Context) {
key := ctx.Request().URL.RawQuery
products := []byte("[]")
err := h.cache.List(ctx.Request().Context(), key, &products)
if err != nil && err != sql.ErrNoRows {
debugf("ProductHandler.List(DB) (%s): %v",
key, err)
writeInternalServerError(ctx)
return
}
ctx.ContentType("application/json")
ctx.Write(products)
}
// Create adds a record to the database.
// Method: POST.
func (h *ProductHandler) Create(ctx iris.Context) {
var product entity.Product
if err := ctx.ReadJSON(&product); err != nil {
return
}
id, err := h.service.Insert(ctx.Request().Context(), product)
if err != nil {
if err == sql.ErrUnprocessable {
ctx.StopWithJSON(iris.StatusUnprocessableEntity, newError(iris.StatusUnprocessableEntity, ctx.Request().Method, ctx.Path(), "required fields are missing"))
return
}
debugf("ProductHandler.Create(DB): %v", err)
writeInternalServerError(ctx)
return
}
// Send 201 with body of {"id":$last_inserted_id"}.
ctx.StatusCode(iris.StatusCreated)
ctx.JSON(iris.Map{product.PrimaryKey(): id})
}
// Update performs a full-update of a record in the database.
// Method: PUT.
func (h *ProductHandler) Update(ctx iris.Context) {
var product entity.Product
if err := ctx.ReadJSON(&product); err != nil {
return
}
affected, err := h.service.Update(ctx.Request().Context(), product)
if err != nil {
if err == sql.ErrUnprocessable {
ctx.StopWithJSON(iris.StatusUnprocessableEntity, newError(iris.StatusUnprocessableEntity, ctx.Request().Method, ctx.Path(), "required fields are missing"))
return
}
debugf("ProductHandler.Update(DB): %v", err)
writeInternalServerError(ctx)
return
}
status := iris.StatusOK
if affected == 0 {
status = iris.StatusNotModified
}
ctx.StatusCode(status)
}
// PartialUpdate is the handler for partially update one or more fields of the record.
// Method: PATCH.
func (h *ProductHandler) PartialUpdate(ctx iris.Context) {
id := ctx.Params().GetInt64Default("id", 0)
var attrs map[string]interface{}
if err := ctx.ReadJSON(&attrs); err != nil {
return
}
affected, err := h.service.PartialUpdate(ctx.Request().Context(), id, attrs)
if err != nil {
if err == sql.ErrUnprocessable {
ctx.StopWithJSON(iris.StatusUnprocessableEntity, newError(iris.StatusUnprocessableEntity, ctx.Request().Method, ctx.Path(), "unsupported value(s)"))
return
}
debugf("ProductHandler.PartialUpdate(DB): %v", err)
writeInternalServerError(ctx)
return
}
status := iris.StatusOK
if affected == 0 {
status = iris.StatusNotModified
}
ctx.StatusCode(status)
}
// Delete removes a record from the database.
// Method: DELETE.
func (h *ProductHandler) Delete(ctx iris.Context) {
id := ctx.Params().GetInt64Default("id", 0)
affected, err := h.service.DeleteByID(ctx.Request().Context(), id)
if err != nil {
debugf("ProductHandler.Delete(DB): %v", err)
writeInternalServerError(ctx)
return
}
status := iris.StatusOK // StatusNoContent
if affected == 0 {
status = iris.StatusNotModified
}
ctx.StatusCode(status)
}