initial commit
This commit is contained in:
commit
213b1aad6c
714 changed files with 590265 additions and 0 deletions
37
service/api/api-context-wrapper.go
Normal file
37
service/api/api-context-wrapper.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"git.sapienzaapps.it/fantasticcoffee/fantastic-coffee-decaffeinated/service/api/reqcontext"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// httpRouterHandler is the signature for functions that accepts a reqcontext.RequestContext in addition to those
|
||||
// required by the httprouter package.
|
||||
type httpRouterHandler func(http.ResponseWriter, *http.Request, httprouter.Params, reqcontext.RequestContext)
|
||||
|
||||
// wrap parses the request and adds a reqcontext.RequestContext instance related to the request.
|
||||
func (rt *_router) wrap(fn httpRouterHandler) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
reqUUID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
rt.baseLogger.WithError(err).Error("can't generate a request UUID")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var ctx = reqcontext.RequestContext{
|
||||
ReqUUID: reqUUID,
|
||||
}
|
||||
|
||||
// Create a request-specific logger
|
||||
ctx.Logger = rt.baseLogger.WithFields(logrus.Fields{
|
||||
"reqid": ctx.ReqUUID.String(),
|
||||
"remote-ip": r.RemoteAddr,
|
||||
})
|
||||
|
||||
// Call the next handler in chain (usually, the handler function for the path)
|
||||
fn(w, r, ps, ctx)
|
||||
}
|
||||
}
|
17
service/api/api-handler.go
Normal file
17
service/api/api-handler.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Handler returns an instance of httprouter.Router that handle APIs registered here
|
||||
func (rt *_router) Handler() http.Handler {
|
||||
// Register routes
|
||||
rt.router.GET("/", rt.getHelloWorld)
|
||||
rt.router.GET("/context", rt.wrap(rt.getContextReply))
|
||||
|
||||
// Special routes
|
||||
rt.router.GET("/liveness", rt.liveness)
|
||||
|
||||
return rt.router
|
||||
}
|
96
service/api/api.go
Normal file
96
service/api/api.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
Package api exposes the main API engine. All HTTP APIs are handled here - so-called "business logic" should be here, or
|
||||
in a dedicated package (if that logic is complex enough).
|
||||
|
||||
To use this package, you should create a new instance with New() passing a valid Config. The resulting Router will have
|
||||
the Router.Handler() function that returns a handler that can be used in a http.Server (or in other middlewares).
|
||||
|
||||
Example:
|
||||
|
||||
// Create the API router
|
||||
apirouter, err := api.New(api.Config{
|
||||
Logger: logger,
|
||||
Database: appdb,
|
||||
})
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("error creating the API server instance")
|
||||
return fmt.Errorf("error creating the API server instance: %w", err)
|
||||
}
|
||||
router := apirouter.Handler()
|
||||
|
||||
// ... other stuff here, like middleware chaining, etc.
|
||||
|
||||
// Create the API server
|
||||
apiserver := http.Server{
|
||||
Addr: cfg.Web.APIHost,
|
||||
Handler: router,
|
||||
ReadTimeout: cfg.Web.ReadTimeout,
|
||||
ReadHeaderTimeout: cfg.Web.ReadTimeout,
|
||||
WriteTimeout: cfg.Web.WriteTimeout,
|
||||
}
|
||||
|
||||
// Start the service listening for requests in a separate goroutine
|
||||
apiserver.ListenAndServe()
|
||||
|
||||
See the `main.go` file inside the `cmd/webapi` for a full usage example.
|
||||
*/
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.sapienzaapps.it/fantasticcoffee/fantastic-coffee-decaffeinated/service/database"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Config is used to provide dependencies and configuration to the New function.
|
||||
type Config struct {
|
||||
// Logger where log entries are sent
|
||||
Logger logrus.FieldLogger
|
||||
|
||||
// Database is the instance of database.AppDatabase where data are saved
|
||||
Database database.AppDatabase
|
||||
}
|
||||
|
||||
// Router is the package API interface representing an API handler builder
|
||||
type Router interface {
|
||||
// Handler returns an HTTP handler for APIs provided in this package
|
||||
Handler() http.Handler
|
||||
|
||||
// Close terminates any resource used in the package
|
||||
Close() error
|
||||
}
|
||||
|
||||
// New returns a new Router instance
|
||||
func New(cfg Config) (Router, error) {
|
||||
// Check if the configuration is correct
|
||||
if cfg.Logger == nil {
|
||||
return nil, errors.New("logger is required")
|
||||
}
|
||||
if cfg.Database == nil {
|
||||
return nil, errors.New("database is required")
|
||||
}
|
||||
|
||||
// Create a new router where we will register HTTP endpoints. The server will pass requests to this router to be
|
||||
// handled.
|
||||
router := httprouter.New()
|
||||
router.RedirectTrailingSlash = false
|
||||
router.RedirectFixedPath = false
|
||||
|
||||
return &_router{
|
||||
router: router,
|
||||
baseLogger: cfg.Logger,
|
||||
db: cfg.Database,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type _router struct {
|
||||
router *httprouter.Router
|
||||
|
||||
// baseLogger is a logger for non-requests contexts, like goroutines or background tasks not started by a request.
|
||||
// Use context logger if available (e.g., in requests) instead of this logger.
|
||||
baseLogger logrus.FieldLogger
|
||||
|
||||
db database.AppDatabase
|
||||
}
|
14
service/api/get-context-reply.go
Normal file
14
service/api/get-context-reply.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"git.sapienzaapps.it/fantasticcoffee/fantastic-coffee-decaffeinated/service/api/reqcontext"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// getContextReply is an example of HTTP endpoint that returns "Hello World!" as a plain text. The signature of this
|
||||
// handler accepts a reqcontext.RequestContext (see httpRouterHandler).
|
||||
func (rt *_router) getContextReply(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
|
||||
w.Header().Set("content-type", "text/plain")
|
||||
_, _ = w.Write([]byte("Hello World!"))
|
||||
}
|
12
service/api/get-hello-world.go
Normal file
12
service/api/get-hello-world.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// getHelloWorld is an example of HTTP endpoint that returns "Hello world!" as a plain text
|
||||
func (rt *_router) getHelloWorld(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
w.Header().Set("content-type", "text/plain")
|
||||
_, _ = w.Write([]byte("Hello World!"))
|
||||
}
|
16
service/api/liveness.go
Normal file
16
service/api/liveness.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// liveness is an HTTP handler that checks the API server status. If the server cannot serve requests (e.g., some
|
||||
// resources are not ready), this should reply with HTTP Status 500. Otherwise, with HTTP Status 200
|
||||
func (rt *_router) liveness(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
/* Example of liveness check:
|
||||
if err := rt.DB.Ping(); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}*/
|
||||
}
|
21
service/api/reqcontext/request-context.go
Normal file
21
service/api/reqcontext/request-context.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
Package reqcontext contains the request context. Each request will have its own instance of RequestContext filled by the
|
||||
middleware code in the api-context-wrapper.go (parent package).
|
||||
|
||||
Each value here should be assumed valid only per request only, with some exceptions like the logger.
|
||||
*/
|
||||
package reqcontext
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// RequestContext is the context of the request, for request-dependent parameters
|
||||
type RequestContext struct {
|
||||
// ReqUUID is the request unique ID
|
||||
ReqUUID uuid.UUID
|
||||
|
||||
// Logger is a custom field logger for the request
|
||||
Logger logrus.FieldLogger
|
||||
}
|
6
service/api/shutdown.go
Normal file
6
service/api/shutdown.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package api
|
||||
|
||||
// Close should close everything opened in the lifecycle of the `_router`; for example, background goroutines.
|
||||
func (rt *_router) Close() error {
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue